C# ‘dynamic’ cannot access properties from anonymous types declared in another assembly
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
![Admin](/assets/logo_square_200-5d0d61d6853298bd2a4fe063103715b4daf2819fc21225efa21dfb93e61952ea.png)
Admin
Updated on July 20, 2020Comments
-
Admin almost 4 years
Code below is working well as long as I have class
ClassSameAssembly
in same assembly as classProgram
. But when I move classClassSameAssembly
to a separate assembly, aRuntimeBinderException
(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 over 13 yearsThanks Jon. I just had the same problem using a class which happened to be private to the assembly.
-
ProfK about 13 yearsI 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 about 13 yearsSorry 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 about 13 yearsThe 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 over 11 yearsThe 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 over 11 yearsJon's answer is more complete, but this actually provides a reasonably simple workaround for me. Thanks :)
-
Kiquenet about 11 yearsany full source code sample for an extension method to convert an anonymous type to an expando ?
-
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 almost 11 yearsI 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 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 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 getToExpando
on 'Anonymous Types' only? -
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 about 10 yearsYes, 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 almost 10 yearsAn 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 over 7 yearsOne note to add here is that you need to restart Visual Studio after this change for this to work.