C# ‘dynamic’ cannot access properties from anonymous types declared in another assembly

39,400

Solution 1

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 :(

Solution 2

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

Solution 3

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

Solution 4

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);
    }

Solution 5

A cleaner solution would be:

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

Which is now an ExpandoObject.

Remember to reference:

Microsoft.CSharp.dll
Share:
39,400
Admin
Author by

Admin

Updated on July 20, 2020

Comments

  • Admin
    Admin almost 4 years

    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
    
  • Dave Markle
    Dave Markle over 13 years
    Thanks Jon. I just had the same problem using a class which happened to be private to the assembly.
  • ProfK
    ProfK about 13 years
    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!
  • Victor
    Victor about 13 years
    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...
  • jbtule
    jbtule about 13 years
    The open source framework impromptu interface does a lot with the dlr it has an inline initialization syntax that works for any object dynamic or static. return Build<ExpandoObject>.NewObject(Name:"Micheal", Age: 20);
  • Jimmy Bosse
    Jimmy Bosse over 11 years
    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.
  • kelloti
    kelloti over 11 years
    Jon's answer is more complete, but this actually provides a reasonably simple workaround for me. Thanks :)
  • Kiquenet
    Kiquenet about 11 years
    any full source code sample for an extension method to convert an anonymous type to an expando ?
  • Jon Skeet
    Jon Skeet about 11 years
    @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.
  • Faisal Mq
    Faisal Mq almost 11 years
    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.
  • Paťo Debute Krehák
    Paťo Debute Krehák almost 11 years
    @FaisalMq it's because the compiler that generate the anonymous classes declares them "internal". Don't know which is the real reason.
  • lbrahim
    lbrahim over 10 years
    @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?
  • Jon Skeet
    Jon Skeet over 10 years
    @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.
  • Igor Mironenko
    Igor Mironenko about 10 years
    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
  • trevorgk
    trevorgk almost 10 years
    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; }
  • Rady
    Rady over 7 years
    One note to add here is that you need to restart Visual Studio after this change for this to work.