Kode di bawah ini berfungsi dengan baik selama saya memiliki kelas ClassSameAssembly
di perakitan yang sama dengan kelas Program
. Tetapi ketika saya memindahkan kelas ClassSameAssembly
ke majelis terpisah, RuntimeBinderException
(lihat di bawah) dilemparkan. Apakah mungkin untuk mengatasinya?
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' tidak mengandung definisi untuk '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
dynamic
c#-4.0
anonymous-types
mehanik
sumber
sumber
Jawaban:
Saya yakin masalahnya adalah bahwa tipe anonim dibuat sebagai
internal
, jadi pengikat tidak benar-benar "tahu" tentang itu.Coba gunakan ExpandoObject sebagai gantinya:
public static dynamic GetValues() { dynamic expando = new ExpandoObject(); expando.Name = "Michael"; expando.Age = 20; return expando; }
Saya tahu itu agak jelek, tapi itu yang terbaik yang dapat saya pikirkan saat ini ... Saya rasa Anda bahkan tidak dapat menggunakan penginisialisasi objek dengannya, karena meskipun diketik dengan kuat sebagai
ExpandoObject
kompiler tidak akan tahu apa yang harus dilakukan dengan "Nama" dan "Usia". Anda mungkin bisa melakukan ini:dynamic expando = new ExpandoObject() { { "Name", "Michael" }, { "Age", 20 } }; return expando;
tapi itu tidak jauh lebih baik ...
Anda berpotensi dapat menulis metode ekstensi untuk mengonversi jenis anonim menjadi perluasan dengan konten yang sama melalui refleksi. Kemudian Anda bisa menulis:
return new { Name = "Michael", Age = 20 }.ToExpando();
Itu sangat mengerikan :(
sumber
return Build<ExpandoObject>.NewObject(Name:"Micheal", Age: 20);
object
atau pada tipe generik (Anda dapat mensyaratkan bahwa itu adalah kelas ...) dan memeriksa tipe pada waktu eksekusi.Anda dapat menggunakan
[assembly: InternalsVisibleTo("YourAssemblyName")]
untuk membuat internal perakitan Anda terlihat.sumber
Saya mengalami masalah serupa dan ingin menambahkan jawaban Jon Skeets bahwa ada opsi lain. Alasan saya mengetahuinya adalah karena saya menyadari bahwa banyak metode ekstensi di Asp MVC3 menggunakan kelas anonim sebagai masukan untuk menyediakan atribut html (baru {alt = "Image alt", style = "padding-top: 5px"} =>
Bagaimanapun - fungsi tersebut menggunakan konstruktor kelas RouteValueDictionary. Saya mencobanya sendiri, dan tentu saja berhasil - meskipun hanya tingkat pertama (saya menggunakan struktur multi-level). SO - dalam kode ini akan menjadi:
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"]);
JADI ... Apa yang sebenarnya terjadi di sini? Mengintip ke dalam RouteValueDictionary mengungkapkan kode ini (nilai ~ = o di atas):
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 - menggunakan TypeDescriptor.GetProperties (o) kita akan bisa mendapatkan properti dan nilai meskipun tipe anonim sedang dibangun sebagai internal dalam rakitan terpisah! Dan tentu saja ini akan sangat mudah untuk diperpanjang agar rekursif. Dan untuk membuat metode ekstensi jika Anda mau.
Semoga ini membantu!
/Pemenang
sumber
Berikut adalah versi dasar dari metode ekstensi untuk ToExpandoObject yang saya yakin memiliki ruang untuk dipoles.
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); }
sumber
Solusi yang lebih bersih adalah:
var d = ClassSameAssembly.GetValues().ToDynamic();
Yang sekarang menjadi ExpandoObject.
Ingatlah untuk referensi:
sumber
Solusi di bawah ini berfungsi untuk saya dalam proyek aplikasi konsol saya
Letakkan [assembly: InternalsVisibleTo ("YourAssemblyName")] ini di \ Properties \ AssemblyInfo.cs dari proyek terpisah dengan fungsi mengembalikan objek dinamis.
"YourAssemblyName" adalah nama assembly dari proyek panggilan. Anda bisa mendapatkannya melalui Assembly.GetExecutingAssembly (). FullName dengan menjalankannya dalam memanggil proyek.
sumber
Metode ekstensi ToExpando (disebutkan dalam jawaban Jon) untuk yang pemberani
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; } }
sumber
Jika Anda sudah menggunakan Newtonsoft.Json dalam proyek Anda (atau Anda ingin menambahkannya untuk tujuan ini), Anda dapat menerapkan metode ekstensi mengerikan yang dirujuk Jon Skeet dalam jawabannya seperti ini:
public static class ObjectExtensions { public static ExpandoObject ToExpando(this object obj) => JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(obj)); }
sumber