C#''dynamic'' no puede acceder a propiedades de tipos anónimos declarados en otro ensamblado
c#-4.0 anonymous-types (8)
El siguiente código funciona bien siempre que tenga la clase ClassSameAssembly
en el mismo ensamblaje que el Program
clase. Pero cuando muevo la clase ClassSameAssembly
a un ensamble separado, se RuntimeBinderException
una RuntimeBinderException
(ver a continuación). ¿Es posible resolverlo?
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'' no contiene una definición para ''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
Aquí hay una versión rudimentaria de un método de extensión para ToExpandoObject que estoy seguro tiene espacio para pulir.
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);
}
Creo que el problema es que el tipo anónimo se genera como internal
, por lo que la carpeta no "sabe" realmente sobre eso como tal.
Intenta usar ExpandoObject en su lugar:
public static dynamic GetValues()
{
dynamic expando = new ExpandoObject();
expando.Name = "Michael";
expando.Age = 20;
return expando;
}
Sé que es algo feo, pero es lo mejor que se me ocurre en este momento ... No creo que puedas usar un inicializador de objetos con él, porque aunque está fuertemente tipeado como ExpandoObject
el compilador no sabrá qué hacer con "Nombre" y "Edad". Puede ser capaz de hacer esto:
dynamic expando = new ExpandoObject()
{
{ "Name", "Michael" },
{ "Age", 20 }
};
return expando;
pero eso no es mucho mejor ...
Usted podría potencialmente escribir un método de extensión para convertir un tipo anónimo a un expando con los mismos contenidos a través de la reflexión. Entonces podrías escribir:
return new { Name = "Michael", Age = 20 }.ToExpando();
Eso es bastante horrible :(
Debajo de la solución funcionó para mí en mis proyectos de aplicaciones de consola
Coloque este [conjunto: InternalsVisibleTo ("YourAssemblyName")] en / Properties / AssemblyInfo.cs del proyecto separado con la función de devolución de objeto dinámico.
"YourAssemblyName" es el nombre del ensamblado del proyecto de llamada. Puede obtenerlo a través de Assembly.GetExecutingAssembly (). FullName ejecutándolo en el proyecto de llamada.
Método de extensión ToExpando (mencionado en la respuesta de Jon) para los valientes
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;
}
}
Me encontré con un problema similar y me gustaría agregarle a Jon Skeets la respuesta de que hay otra opción. La razón por la que me enteré fue que me di cuenta de que muchos métodos de extensión en Asp MVC3 usan clases anónimas como entrada para proporcionar atributos html (new {alt = "Image alt", style = "padding-top: 5px"} =>
De todos modos, esas funciones usan el constructor de la clase RouteValueDictionary. Lo intenté yo mismo y, por supuesto, funciona, aunque solo sea en el primer nivel (utilicé una estructura de varios niveles). SO - en el código esto sería:
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"]);
Entonces ... ¿Qué está pasando realmente aquí? Un vistazo dentro de RouteValueDictionary revela este código (valores ~ = o arriba):
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 - usando TypeDescriptor.GetProperties (o) podríamos obtener las propiedades y valores a pesar de que el tipo anónimo se construye como interno en un ensamblado separado. Y, por supuesto, esto sería bastante fácil de ampliar para que sea recursivo. Y para hacer un método de extensión si lo desea.
¡Espero que esto ayude!
/Víctor
Puede usar [assembly: InternalsVisibleTo("YourAssemblyName")]
para que sus elementos internos de ensamblaje sean visibles.
Si ya está utilizando Newtonsoft.Json en su proyecto (o está dispuesto a agregarlo para este fin), podría implementar ese horrible método de extensión al que se está refiriendo Jon Skeet en su respuesta de esta manera:
public static class ObjectExtensions
{
public static ExpandoObject ToExpando(this object obj)
=> JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(obj));
}
Una solución más limpia sería:
var d = ClassSameAssembly.GetValues().ToDynamic();
Que ahora es un ExpandoObject.
Recuerde hacer referencia:
Microsoft.CSharp.dll