C # 'dinamis' tidak dapat mengakses properti dari jenis anonim yang dideklarasikan di rakitan lain

87

Kode di bawah ini berfungsi dengan baik selama saya memiliki kelas ClassSameAssemblydi perakitan yang sama dengan kelas Program. Tetapi ketika saya memindahkan kelas ClassSameAssemblyke 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
mehanik
sumber
StackTrace: di CallSite.Target (Closure, CallSite, Object) di System.Dynamic.UpdateDelegates.UpdateAndExecute1 [T0, TRet] (situs CallSite, T0 arg0) di ConsoleApplication2.Program.Main (String [] args) di C: \ temp \ Projects \ ConsoleApplication2 \ ConsoleApplication2 \ Program.cs: baris 23 di System.AppDomain._nExecuteAssembly (perakitan RuntimeAssembly, String [] args) di System.AppDomain.nExecuteAssembly (perakitan RuntimeAssembly, String [] args) di System.AppDomain.ExecuteAssembly ( String assemblyFile, Evidence assemblySecurity, String [] args)
mehanik
di Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly () di System.Threading.ThreadHelper.ThreadStart_Context (status Objek) di System.Threading.ExecutionContext.Run (ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx.) ExecutionContext.Run (ExecutionContext executionContext, ContextCallback callback, Object state) di System.Threading.ThreadHelper.ThreadStart () InnerException:
mehanik
ada solusi akhir dengan kode sumber lengkap?
Kiquenet

Jawaban:

116

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 ExpandoObjectkompiler 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 :(

Jon Skeet
sumber
1
Terima kasih Jon. Saya baru saja mengalami masalah yang sama saat menggunakan kelas yang kebetulan bersifat pribadi bagi majelis.
Dave Markle
2
Saya akan menyukai sesuatu seperti contoh mengerikan Anda di akhir, tidak seburuk itu. Untuk menggunakan: dynamic props = new {Metadata = DetailModelMetadata.Create, PageTitle = "New Content", PageHeading = "Content Management"}; dan memiliki properti bernama ditambahkan sebagai anggota dinamis akan sangat bagus!
ProfK
Antarmuka dadakan kerangka kerja sumber terbuka melakukan banyak hal dengan dlr yang memiliki sintaks inisialisasi sebaris yang berfungsi untuk objek apa pun yang dinamis atau statis. return Build<ExpandoObject>.NewObject(Name:"Micheal", Age: 20);
jbtule
1
contoh kode sumber lengkap apa pun untuk metode ekstensi untuk mengonversi jenis anonim menjadi perluasan?
Kiquenet
1
@ Md.lbrahim: Pada dasarnya Anda tidak bisa. Anda harus melakukannya baik pada objectatau pada tipe generik (Anda dapat mensyaratkan bahwa itu adalah kelas ...) dan memeriksa tipe pada waktu eksekusi.
Jon Skeet
63

Anda dapat menggunakan [assembly: InternalsVisibleTo("YourAssemblyName")]untuk membuat internal perakitan Anda terlihat.

ema
sumber
2
Jawaban Jon lebih lengkap, tetapi ini sebenarnya memberikan solusi yang cukup sederhana untuk saya. Terima kasih :)
kelloti
Saya membenturkan kepala selama berjam-jam di forum yang berbeda tetapi tidak menemukan jawaban sederhana kecuali yang ini. Terima kasih Luke. Tapi tetap saja saya tidak mengerti mengapa tipe dinamis tidak dapat diakses di luar rakitan seperti di rakitan yang sama? Maksud saya mengapa pembatasan ini di .Net.
Faisal Mq
@FaisalMq itu karena kompilator yang menghasilkan kelas anonim mendeklarasikannya "internal". Tidak tahu yang mana alasan sebenarnya.
ema
2
Ya, saya pikir jawaban ini penting, karena saya tidak ingin mengubah kode kerja, saya hanya perlu mengujinya dari perakitan lain
PandaWood
Satu catatan untuk ditambahkan di sini adalah Anda perlu me-restart Visual Studio setelah perubahan ini untuk bekerja.
Rady
11

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

Pemenang
sumber
Maaf atas kebingungan itu. Kode diperbarui dari prop1 => p1 jika sesuai. Tetap saja - gagasan dengan seluruh posting adalah untuk mengedepankan TypeDescriptor.GetProperties sebagai opsi untuk menyelesaikan masalah, yang semoga tetap jelas ...
Victor
Ini benar-benar bodoh karena dinamika tidak dapat melakukan ini untuk kita. Saya berdua sangat mencintai dan sangat membenci dinamika.
Chris Marisic
2

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);
    }
Ryan Rodemoyer
sumber
1

Solusi yang lebih bersih adalah:

var d = ClassSameAssembly.GetValues().ToDynamic();

Yang sekarang menjadi ExpandoObject.

Ingatlah untuk referensi:

Microsoft.CSharp.dll
Zylv3r
sumber
1

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.

Shah
sumber
0

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;
    }
}
Matas Vaitkevicius
sumber
0

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));
}
huysentruitw
sumber