Attempting to bind a dynamic method on a dynamically-created assembly causes a RuntimeBinderException
Asked Answered
L

2

9

I have a handy utility method which takes code and spits out an in-memory assembly. (It uses CSharpCodeProvider, although I don't think that should matter.) This assembly works like any other with reflection, but when used with the dynamic keyword, it seems to fail with a RuntimeBinderException:

'object' does not contain a definition for 'Sound'

Example:

var assembly = createAssembly("class Dog { public string Sound() { return \"woof\"; } }");
var type = assembly.GetType("Dog");
Object dog = Activator.CreateInstance(type);

var method = type.GetMethod("Sound");
var test1Result = method.Invoke(dog, null); //This returns "woof", as you'd expect

dynamic dog2 = dog;
String test2Result = dog2.Sound(); //This throws a RuntimeBinderException

Does anyone know the reason why the DLR is not able to handle this? Is there anything that could be done to fix this scenario?

EDIT:

createAssembly method:

Disclaimer: some of this stuff contains extension methods, custom types, etc. It should be self-explanatory though.

private Assembly createAssembly(String source, IEnumerable<String> assembliesToReference = null)
{
    //Create compiler
    var codeProvider = new CSharpCodeProvider();

    //Set compiler parameters
    var compilerParameters = new CompilerParameters
    {
        GenerateInMemory = true,
        GenerateExecutable = false,
        CompilerOptions = "/optimize",
    };

    //Get the name of the current assembly and everything it references
    if (assembliesToReference == null)
    {
        var executingAssembly = Assembly.GetExecutingAssembly();
        assembliesToReference = executingAssembly
            .AsEnumerable()
            .Concat(
                executingAssembly
                    .GetReferencedAssemblies()
                    .Select(a => Assembly.Load(a))
            )
            .Select(a => a.Location);
     }//End if

    compilerParameters.ReferencedAssemblies.AddRange(assembliesToReference.ToArray());

    //Compile code
    var compilerResults = codeProvider.CompileAssemblyFromSource(compilerParameters, source);

    //Throw errors
    if (compilerResults.Errors.Count != 0)
    {                
        throw new CompilationException(compilerResults.Errors);                
    }

    return compilerResults.CompiledAssembly;
}
Lilley answered 17/4, 2013 at 16:2 Comment(0)
S
5

Make your class public.

var assembly = createAssembly("public class Dog { public string Sound() ...
                               ^

That solves the problem on my machine.

Singles answered 17/4, 2013 at 17:31 Comment(1)
Silly mistake on my part. dynamic respects the accessibility, so it can't execute non-public code from another assembly, while reflection doesn't care one bit about accessibility. Thanks.Lilley
B
0

This could be a workarround.

//instead of this
//dynamic dog2 = dog;    

//try this
dynamic dog2 = DynWrapper(dog);    
String test2Result = dog2.Sound();//Now this works.

public class DynWrapper : DynamicObject {
        private readonly object _target;


        public DynWrapper(object target) {
            _target = target;                
        }

        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) {   
            //for the sake of simplicity I'm not taking arguments into account,
            //of course you should in a real app.
            var mi = _target.GetType().GetMethod(binder.Name);
            if (mi != null) {
                result = mi.Invoke(_target, null);                                        
                return true;
            }
            return base.TryInvokeMember(binder, args, out result);
        }
    }

PS: I try to post this as a comment (since it's a workarround not an answer) but couldn't do that beacuse it's to long....

Bear answered 17/4, 2013 at 17:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.