El código a continuación funciona bien siempre que tenga una clase ClassSameAssembly
en el mismo ensamblado que la clase Program
. Pero cuando muevo la clase ClassSameAssembly
a un ensamblado separado, RuntimeBinderException
se lanza un (ver más abajo). ¿Es posible solucionarlo?
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
: 'objeto' no contiene una definición para 'Nombre'
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
dynamic
c#-4.0
anonymous-types
mehanik
fuente
fuente
Respuestas:
Creo que el problema es que el tipo anónimo se genera como
internal
, por lo que el archivador no lo "sabe" realmente como tal.Intente 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 objeto con él, porque aunque está fuertemente tipado,
ExpandoObject
el compilador no sabrá qué hacer. con "Nombre" y "Edad". Es posible que pueda hacer esto:dynamic expando = new ExpandoObject() { { "Name", "Michael" }, { "Age", 20 } }; return expando;
pero eso no es mucho mejor ...
Potencialmente, podría escribir un método de extensión para convertir un tipo anónimo en un expando con el mismo contenido a través de la reflexión. Entonces podrías escribir:
return new { Name = "Michael", Age = 20 }.ToExpando();
Aunque eso es bastante horrible :(
fuente
return Build<ExpandoObject>.NewObject(Name:"Micheal", Age: 20);
object
o en un tipo genérico (puede requerir que sea una clase ...) y verificar el tipo en el momento de la ejecución.Puede utilizar
[assembly: InternalsVisibleTo("YourAssemblyName")]
para hacer visibles los componentes internos del ensamblaje.fuente
Me encontré con un problema similar y me gustaría agregar a la respuesta de Jon Skeets 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 (nuevo {alt = "Image alt", style = "padding-top: 5px"} =>
De todos modos, esas funciones usan el constructor de la clase RouteValueDictionary. Lo intenté yo mismo, y efectivamente funciona, aunque solo en el primer nivel (utilicé una estructura de varios niveles). Entonces, 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); }
Entonces, usando TypeDescriptor.GetProperties (o) podríamos obtener las propiedades y los valores a pesar de que el tipo anónimo se construye como interno en un ensamblaje separado. Y, por supuesto, esto sería bastante fácil de ampliar para hacerlo recursivo. Y para hacer un método de extensión si quisiera.
¡Espero que esto ayude!
/Víctor
fuente
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); }
fuente
Una solución más limpia sería:
var d = ClassSameAssembly.GetValues().ToDynamic();
Que ahora es un ExpandoObject.
Recuerde hacer referencia:
fuente
La siguiente solución funcionó para mí en mis proyectos de aplicación de consola
Ponga este [ensamblado: InternalsVisibleTo ("YourAssemblyName")] en \ Properties \ AssemblyInfo.cs del proyecto separado con la función que devuelve el objeto dinámico.
"YourAssemblyName" es el nombre de ensamblado del proyecto de llamada. Puede obtenerlo a través de Assembly.GetExecutingAssembly (). FullName ejecutándolo en la llamada al proyecto.
fuente
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; } }
fuente
Si ya está utilizando Newtonsoft.Json en su proyecto (o está dispuesto a agregarlo para este propósito), podría implementar ese horrible método de extensión al que se refiere 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)); }
fuente