92

Code below is working well as long as I have class ClassSameAssembly in same assembly as class Program. But when I move class ClassSameAssembly to a separate assembly, a RuntimeBinderException (see below) is thrown. Is it possible to resolve it?

using System;

namespace ConsoleApplication2
{
    public static class ClassSameAssembly
    {
        public static dynamic GetValues()
        {
            return new
            {
                Name = "Michael", Age = 20
            };
        }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            var d = ClassSameAssembly.GetValues();
            Console.WriteLine("{0} is {1} years old", d.Name, d.Age);
        }
    }
}

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'object' does not contain a definition for 'Name'

at CallSite.Target(Closure , CallSite , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
at ConsoleApplication2.Program.Main(String[] args) in C:\temp\Projects\ConsoleApplication2\ConsoleApplication2\Program.cs:line 23
CodeFox
  • 3,321
  • 1
  • 29
  • 41
mehanik
  • 1,067
  • 2
  • 9
  • 11
  • StackTrace: at CallSite.Target(Closure , CallSite , Object ) at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0) at ConsoleApplication2.Program.Main(String[] args) in C:\temp\Projects\ConsoleApplication2\ConsoleApplication2\Program.cs:line 23 at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args) at System.AppDomain.nExecuteAssembly(RuntimeAssembly assembly, String[] args) at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) – mehanik Apr 13 '10 at 14:39
  • at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() InnerException: – mehanik Apr 13 '10 at 14:40
  • any final solution with full source code? – Kiquenet Apr 22 '13 at 12:23

8 Answers8

120

I believe the problem is that the anonymous type is generated as internal, so the binder doesn't really "know" about it as such.

Try using ExpandoObject instead:

public static dynamic GetValues()
{
    dynamic expando = new ExpandoObject();
    expando.Name = "Michael";
    expando.Age = 20;
    return expando;
}

I know that's somewhat ugly, but it's the best I can think of at the moment... I don't think you can even use an object initializer with it, because while it's strongly typed as ExpandoObject the compiler won't know what to do with "Name" and "Age". You may be able to do this:

 dynamic expando = new ExpandoObject()
 {
     { "Name", "Michael" },
     { "Age", 20 }
 };
 return expando;

but that's not much better...

You could potentially write an extension method to convert an anonymous type to an expando with the same contents via reflection. Then you could write:

return new { Name = "Michael", Age = 20 }.ToExpando();

That's pretty horrible though :(

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    Thanks Jon. I just had the same problem using a class which happened to be private to the assembly. – Dave Markle Jan 28 '11 at 15:34
  • 2
    I would love something like your horrible example at the end though, just not as horrible. To use: dynamic props = new {Metadata = DetailModelMetadata.Create, PageTitle = "New Content", PageHeading = "Content Management"}; and have the named props added as dynamic members would be great! – ProfK Apr 26 '11 at 20:15
  • The open source framework [impromptu interface](http://code.google.com/p/impromptu-interface/) does a lot with the dlr it has an [inline initialization syntax](http://code.google.com/p/impromptu-interface/wiki/UsageBuilder#Simplified_ImpromptuBuilder) that works for any object dynamic or static. `return Build.NewObject(Name:"Micheal", Age: 20);` – jbtule Jun 07 '11 at 12:39
  • The problem with this solution is that the ExpandoObject will serialize as an array of key value pairs while the anonymous type will serialize s expected. Using the InternalsVisibleTo will remove the error, but this won't work if you expect your object to be consumed by unknown sources. In my case I needed to allow my unit tests to be able access the properties and I needed to serialize the object to JSON so the InternalsVisibleTo was a better solution for my use case. – Jimmy Bosse Oct 19 '12 at 17:23
  • 1
    any full source code sample for an extension method to convert an anonymous type to an expando ? – Kiquenet Apr 22 '13 at 12:26
  • @Kiquenet: Not that I know offhand, but it should be pretty simple just using Type.GetProperties and iterating over the results, fetching them and putting them into the Expando as a dictionary. – Jon Skeet Apr 22 '13 at 12:36
  • @JonSkeet How do I write extension method for 'Anonymous Types'? Like `public static dynamic ToExpando(this ? dynamicObject){//Do Sth}` How do I tell it to get `ToExpando` on 'Anonymous Types' only? – lbrahim Mar 31 '14 at 09:13
  • 1
    @Md.lbrahim: You can't, basically. You'd have to do it either on `object` or on a generic type (you can require that it's a class...) and check the type at execution time. – Jon Skeet Mar 31 '14 at 09:17
  • An annoying problem, I put the repetive logic into following method: private static dynamic Expando(string blurb, string imageSrc, string navUrl) { dynamic expando = new ExpandoObject(); expando.Blurb = blurb; expando.ImageSrc = imageSrc; expando.NavigateUrl = navUrl; return expando; } – trevorgk Aug 01 '14 at 04:05
63

You could use [assembly: InternalsVisibleTo("YourAssemblyName")] to make you assembly internals visible.

undefined
  • 33,537
  • 22
  • 129
  • 198
ema
  • 5,668
  • 1
  • 25
  • 31
  • 2
    Jon's answer is more complete, but this actually provides a reasonably simple workaround for me. Thanks :) – kelloti Oct 29 '12 at 19:51
  • I was banging my head for hours on different forums but found no simple answer except this one. Thanks Luke. But still I cannot understand why a dynamic type is not accessible outside an assembly like it does in same assembly? I mean why is this restriction in .Net. – Faisal Mq Sep 09 '13 at 13:03
  • @FaisalMq it's because the compiler that generate the anonymous classes declares them "internal". Don't know which is the real reason. – ema Sep 10 '13 at 08:34
  • 2
    Yes, I think this answer is important, because I don't want to change working code, I just need to test it from another assembly – PandaWood May 30 '14 at 02:44
  • One note to add here is that you need to restart Visual Studio after this change for this to work. – Rady Oct 09 '16 at 17:44
11

I ran into a similair problem and would like to add to Jon Skeets answer that there is another option. The reason I found out was that I realized that many extension methods in Asp MVC3 uses anonymous classes as input to provide html attributes (new {alt="Image alt", style="padding-top: 5px"} =>

Anyway - those functions use the constructor of the RouteValueDictionary class. I tried that myself, and sure enough it works - though only the first level (I used a multi-level structure). SO - in code this would be:

object o = new {
    name = "theName",
    props = new {
        p1 = "prop1",
        p2 = "prop2"
    }
}
SeparateAssembly.TextFunc(o)

//In SeparateAssembly:
public void TextFunc(Object o) {
  var rvd = new RouteValueDictionary(o);

//Does not work:
Console.WriteLine(o.name);
Console.WriteLine(o.props.p1);

//DOES work!
Console.WriteLine(rvd["name"]);

//Does not work
Console.WriteLine(rvd["props"].p1);
Console.WriteLine(rvd["props"]["p1"]);

SO... What is really going on here? A peek inside the RouteValueDictionary reveals this code (values ~= o above):

foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(values))
    object obj2 = descriptor.GetValue(values);
    //"this.Add" would of course need to be adapted
    this.Add(descriptor.Name, obj2);
}

SO - using TypeDescriptor.GetProperties(o) we would be able to get the properties and values despite the anonymous type being constructed as internal in a separate assembly! And of course this would be quite easy to extend to make it recursive. And to make an extension method if you wanted.

Hope this helps!

/Victor

Victor
  • 3,999
  • 3
  • 24
  • 27
  • Sorry for that confusion. Code updated from prop1 => p1 where appropriate. Still - the idea with the entire post was to put forward TypeDescriptor.GetProperties as an option to solve the problem, which hopefully was clear anyway... – Victor Jun 07 '11 at 12:32
3

Here is a rudimentary version of an extension method for ToExpandoObject that I'm sure has room for polishing.

    public static ExpandoObject ToExpandoObject(this object value)
    {
        // Throw is a helper in my project, replace with your own check(s)
        Throw<ArgumentNullException>.If(value, Predicates.IsNull, "value");

        var obj = new ExpandoObject() as IDictionary<string, object>;

        foreach (var property in value.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            obj.Add(property.Name, property.GetValue(value, null));
        }

        return obj as ExpandoObject;
    }

    [TestCase(1, "str", 10.75, 9.000989, true)]
    public void ToExpandoObjectTests(int int1, string str1, decimal dec1, double dbl1, bool bl1)
    {
        DateTime now = DateTime.Now;

        dynamic value = new {Int = int1, String = str1, Decimal = dec1, Double = dbl1, Bool = bl1, Now = now}.ToExpandoObject();

        Assert.AreEqual(int1, value.Int);
        Assert.AreEqual(str1, value.String);
        Assert.AreEqual(dec1, value.Decimal);
        Assert.AreEqual(dbl1, value.Double);
        Assert.AreEqual(bl1, value.Bool);
        Assert.AreEqual(now, value.Now);
    }
Ryan Rodemoyer
  • 5,548
  • 12
  • 44
  • 54
2

Below solution worked for me in my console application projects

Put this [assembly: InternalsVisibleTo("YourAssemblyName")] in \Properties\AssemblyInfo.cs of the separate project with function returning dynamic object.

"YourAssemblyName" is the assembly name of calling project. You can get that through Assembly.GetExecutingAssembly().FullName by executing it in calling project.

Shah
  • 21
  • 2
1

A cleaner solution would be:

var d = ClassSameAssembly.GetValues().ToDynamic();

Which is now an ExpandoObject.

Remember to reference:

Microsoft.CSharp.dll
Zylv3r
  • 21
  • 3
0

ToExpando extension method (mentioned in Jon's answer) for the brave ones

public static class ExtensionMethods
{
    public static ExpandoObject ToExpando(this object obj)
    {
        IDictionary<string, object> expando = new ExpandoObject();
        foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(obj))
        {
            var value = propertyDescriptor.GetValue(obj);
            expando.Add(propertyDescriptor.Name, value == null || new[]
            {
                typeof (Enum),
                typeof (String),
                typeof (Char),
                typeof (Guid),
                typeof (Boolean),
                typeof (Byte),
                typeof (Int16),
                typeof (Int32),
                typeof (Int64),
                typeof (Single),
                typeof (Double),
                typeof (Decimal),
                typeof (SByte),
                typeof (UInt16),
                typeof (UInt32),
                typeof (UInt64),
                typeof (DateTime),
                typeof (DateTimeOffset),
                typeof (TimeSpan),
            }.Any(oo => oo.IsInstanceOfType(value))
                ? value
                : value.ToExpando());
        }

        return (ExpandoObject)expando;
    }
}
Matas Vaitkevicius
  • 58,075
  • 31
  • 238
  • 265
0

If you're already using Newtonsoft.Json in your project (or you're willing to add it for this purpose), you could implement that horrible extension method Jon Skeet is referring to in his answer like this:

public static class ObjectExtensions
{
    public static ExpandoObject ToExpando(this object obj)
        => JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(obj));
}
huysentruitw
  • 27,376
  • 9
  • 90
  • 133