Poniższy kod działa dobrze, o ile mam klasę ClassSameAssembly
w tym samym zestawie co klasa Program
. Ale kiedy przenoszę klasę ClassSameAssembly
do oddzielnego zestawu, RuntimeBinderException
rzucany jest znak (patrz poniżej). Czy można to rozwiązać?
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
: „obiekt” nie zawiera definicji dla „nazwy”
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
źródło
źródło
Odpowiedzi:
Uważam, że problem polega na tym, że typ anonimowy jest generowany jako
internal
, więc segregator tak naprawdę nie „wie” o nim jako takim.Zamiast tego spróbuj użyć ExpandoObject:
public static dynamic GetValues() { dynamic expando = new ExpandoObject(); expando.Name = "Michael"; expando.Age = 20; return expando; }
Wiem, że to trochę brzydkie, ale jest to najlepsze, o czym w tej chwili przychodzi mi do głowy ... Nie sądzę, aby można było nawet użyć z nim inicjatora obiektu, ponieważ podczas gdy jest mocno wpisany, ponieważ
ExpandoObject
kompilator nie będzie wiedział, co robić z „imieniem” i „wiekiem”. Państwo może być w stanie to zrobić:dynamic expando = new ExpandoObject() { { "Name", "Michael" }, { "Age", 20 } }; return expando;
ale to nie jest dużo lepsze ...
Możesz potencjalnie napisać metodę rozszerzenia, aby przekonwertować typ anonimowy na expando z tą samą zawartością przez odbicie. Wtedy możesz napisać:
return new { Name = "Michael", Age = 20 }.ToExpando();
Ale to dość okropne :(
źródło
return Build<ExpandoObject>.NewObject(Name:"Micheal", Age: 20);
object
typie ogólnym lub na typie ogólnym (możesz wymagać, aby był to klasa ...) i sprawdzić typ w czasie wykonywania.Możesz użyć,
[assembly: InternalsVisibleTo("YourAssemblyName")]
aby wyświetlić wewnętrzne elementy zespołu.źródło
Napotkałem podobny problem i chciałbym dodać do Jona Skeetsa odpowiedź, że istnieje inna opcja. Powodem, dla którego się dowiedziałem, było to, że zdałem sobie sprawę, że wiele metod rozszerzających w Asp MVC3 używa anonimowych klas jako danych wejściowych do dostarczania atrybutów html (new {alt = "Image alt", style = "padding-top: 5px"} =>
W każdym razie - te funkcje używają konstruktora klasy RouteValueDictionary. Sam spróbowałem i na pewno działa - choć tylko na pierwszym poziomie (użyłem konstrukcji wielopoziomowej). SO - w kodzie będzie to:
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"]);
WIĘC ... Co tu się naprawdę dzieje? Wgląd do RouteValueDictionary ujawnia ten kod (wartości ~ = o powyżej):
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 - używając TypeDescriptor.GetProperties (o) bylibyśmy w stanie uzyskać właściwości i wartości pomimo tego, że typ anonimowy był konstruowany jako wewnętrzny w oddzielnym zestawie! Oczywiście byłoby to dość łatwe do rozszerzenia, aby było rekurencyjne. I zrobić metodę rozszerzenia, jeśli chcesz.
Mam nadzieję że to pomoże!
/Zwycięzca
źródło
Oto podstawowa wersja metody rozszerzenia ToExpandoObject, która z pewnością ma miejsce do polerowania.
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); }
źródło
Czystszym rozwiązaniem byłoby:
var d = ClassSameAssembly.GetValues().ToDynamic();
Który jest teraz ExpandoObject.
Pamiętaj, aby odnieść się do:
źródło
Poniższe rozwiązanie sprawdziło się w moich projektach aplikacji konsolowych
Umieść to [assembly: InternalsVisibleTo ("YourAssemblyName")] w \ Properties \ AssemblyInfo.cs oddzielnego projektu z funkcją zwracającą obiekt dynamiczny.
„YourAssemblyName” to nazwa zestawu wywołującego projekt. Możesz to uzyskać poprzez Assembly.GetExecutingAssembly (). FullName, wykonując to w wywołaniu projektu.
źródło
Metoda rozszerzenia ToExpando (wspomniana w odpowiedzi Jona) dla odważnych
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; } }
źródło
Jeśli już używasz Newtonsoft.Json w swoim projekcie (lub chcesz dodać go w tym celu), możesz zaimplementować tę okropną metodę rozszerzenia, do której odwołuje się Jon Skeet w swojej odpowiedzi :
public static class ObjectExtensions { public static ExpandoObject ToExpando(this object obj) => JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(obj)); }
źródło