C # 'dinamik', başka bir derlemede bildirilen anonim türlerdeki özelliklere erişemez


87

Aşağıdaki kod, sınıfla ClassSameAssemblyaynı derlemede sınıfım olduğu sürece iyi çalışıyor Program. Ancak sınıfı ClassSameAssemblyayrı bir derlemeye taşıdığımda , a RuntimeBinderException(aşağıya bakın) atılıyor. Çözmek mümkün mü?

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: 'nesne', 'Ad' için bir tanım içermiyor

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

StackTrace: System.Dynamic.UpdateDelegates.UpdateAndExecute1 [T0, TRet] (CallSite sitesi, T0 arg0) at CallSite.Target (Closure, CallSite, Object) at ConsoleApplication2.Program.Main (String [] değiştirgeler) C: \ temp \ Projects \ ConsoleApplication2 \ ConsoleApplication2 \ Program.cs: System.AppDomain._nExecuteAssembly konumundaki satır 23 (RuntimeAssembly, String [] args) System.AppDomain.nExecuteAssembly (RuntimeAssembly, String [] args) System.AppDomain.ExecuteAssembly ( Yaylı assemblyFile, kanıt assemblySecurity, string [] args)
Mehanik

Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly () at System.Threading.ThreadHelper.ThreadStart_Context (Object state) at System.Threading.ExecutionContext.Run (ExecutionContext executionContext, ContextCallback geri çağırma, Nesne durumu, Boolex göz ardı etme). ExecutionContext.Run (ExecutionContext executionContext, ContextCallback geri arama, Object durumu) System.Threading.ThreadHelper.ThreadStart () InnerException:
mehanik

tam kaynak kodlu herhangi bir nihai çözüm var mı?
Kiquenet

Yanıtlar:


116

Sorunun, anonim tipin olarak oluşturulduğuna inanıyorum internal, bu yüzden bağlayıcı gerçekten "bilmiyor".

Bunun yerine ExpandoObject kullanmayı deneyin:

public static dynamic GetValues()
{
    dynamic expando = new ExpandoObject();
    expando.Name = "Michael";
    expando.Age = 20;
    return expando;
}

Bunun biraz çirkin olduğunu biliyorum, ama şu anda düşünebildiğim en iyisi ... Onunla bir nesne başlatıcı bile kullanabileceğinizi sanmıyorum, çünkü güçlü bir şekilde yazılmışken ExpandoObjectderleyici ne yapacağını bilemeyecek "Ad" ve "Yaş" ile. Bunu yapabilirsiniz :

 dynamic expando = new ExpandoObject()
 {
     { "Name", "Michael" },
     { "Age", 20 }
 };
 return expando;

ama bu daha iyi değil ...

Sen olabilir , potansiyel olarak yansıma yoluyla aynı içerikli bir EXPANDO isimsiz bir türünü dönüştürmek için bir uzantısı yöntem yazmak. O zaman yazabilirsin:

return new { Name = "Michael", Age = 20 }.ToExpando();

Yine de bu oldukça korkunç :(


1
Teşekkürler Jon. Meclise özel olan bir sınıfı kullanırken aynı sorunu yaşadım.
Dave Markle

2
Sonunda sizin korkunç örneğiniz gibi bir şeyi seveceğim, ama o kadar da korkunç değil. Kullanmak için: dynamic props = new {Metadata = DetailModelMetadata.Create, PageTitle = "Yeni İçerik", PageHeading = "İçerik Yönetimi"}; ve dinamik üyeler olarak adlandırılmış sahne donanımlarını eklemek harika olurdu!
ProfK

Açık kaynaklı çerçeve doğaçlama arayüzü dlr ile çok şey yapar , herhangi bir nesne dinamik veya statik için çalışan bir satır içi başlatma sözdizimine sahiptir. return Build<ExpandoObject>.NewObject(Name:"Micheal", Age: 20);
jbtule

1
anonim bir türü expando'ya dönüştürmek için bir uzantı yöntemi için herhangi bir tam kaynak kodu örneği?
Kiquenet

1
@ Md.lbrahim: Temelde yapamazsınız. Bunu objectgenel bir tür üzerinde veya bir tür üzerinde yapmanız (bunun bir sınıf olmasını isteyebilirsiniz ...) ve çalıştırma sırasında türü kontrol etmeniz gerekir.
Jon Skeet

63

[assembly: InternalsVisibleTo("YourAssemblyName")]Montaj iç elemanlarını görünür kılmak için kullanabilirsiniz .


2
Jon'un cevabı daha eksiksiz, ancak bu aslında benim için oldukça basit bir çözüm sağlıyor. Teşekkürler :)
kelloti

Farklı forumlarda saatlerce kafamı salladım ama bunun dışında basit bir cevap bulamadım. Teşekkürler Luke. Ama yine de dinamik bir tipin aynı montajda olduğu gibi bir montaj dışında neden erişilebilir olmadığını anlayamıyorum? Yani neden bu kısıtlama .Net'te.
Faisal Mq

@FaisalMq bunun nedeni anonim sınıfları oluşturan derleyicinin onları "dahili" olarak bildirmesidir. Hangisinin gerçek neden olduğunu bilmiyorum.
ema

2
Evet, bu cevabın önemli olduğunu düşünüyorum, çünkü çalışma kodunu değiştirmek istemiyorum, sadece başka bir derlemeden test etmem gerekiyor
PandaWood,

Buraya eklenecek bir not, bunun çalışması için bu değişiklikten sonra Visual Studio'yu yeniden başlatmanız gerektiğidir.
Rady

11

Benzer bir problemle karşılaştım ve Jon Skeets'in cevabına başka bir seçenek daha olduğunu eklemek istiyorum. Bulmamın nedeni, Asp MVC3'teki birçok uzantı yönteminin html öznitelikleri sağlamak için giriş olarak anonim sınıfları kullandığını fark etmemdi (yeni {alt = "Image alt", style = "padding-top: 5px"} =>

Her neyse - bu işlevler RouteValueDictionary sınıfının yapıcısını kullanır. Bunu kendim denedim ve kesinlikle işe yarıyor - sadece ilk seviye olsa da (çok seviyeli bir yapı kullandım). SO - kodda bu şöyle olacaktır:

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"]);

SO ... Burada gerçekten neler oluyor? RouteValueDictionary içine bir göz atmak bu kodu gösterir (yukarıdaki ~ = o değerleri):

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 - TypeDescriptor.GetProperties (o) kullanarak, anonim tip ayrı bir derlemede dahili olarak inşa edilmesine rağmen özellikleri ve değerleri elde edebilirdik! Ve elbette bunu yinelemeli hale getirmek için genişletmek oldukça kolay olacaktır. Ve isterseniz bir uzatma yöntemi yapmak.

Bu yardımcı olur umarım!

/ Victor


Bu karışıklık için özür dilerim. Uygun olduğunda prop1 => p1'den güncellenen kod. Yine de - tüm yazıyla ilgili fikir, sorunu çözmek için bir seçenek olarak TypeDescriptor.GetProperties'i öne sürmekti, umarım yine de açıktı ...
Victor

Bu, dinamiğin bunu bizim için yapamayacağı gerçekten aptalca. Hem dinamiği gerçekten seviyorum hem de gerçekten nefret ediyorum.
Chris Marisic

2

İşte ToExpandoObject için bir uzatma yönteminin ilkel bir versiyonu, eminim ki parlatma için yer vardır.

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

1

Daha temiz bir çözüm şu şekilde olacaktır:

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

Bu artık bir ExpandoObject.

Referans vermeyi unutmayın:

Microsoft.CSharp.dll

1

Konsol uygulama projelerimde aşağıdaki çözüm benim için çalıştı

Bu [assembly: InternalsVisibleTo ("YourAssemblyName")] işlevini dinamik nesne döndüren ayrı bir projenin \ Properties \ AssemblyInfo.cs içine koyun.

"YourAssemblyName", çağıran projenin derleme adıdır. Bunu Assembly.GetExecutingAssembly (). FullName aracılığıyla proje çağrısında çalıştırarak elde edebilirsiniz.


0

Cesur olanlar için ToExpando uzatma yöntemi (Jon'un cevabında bahsedilmiştir)

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;
    }
}

0

Projenizde zaten Newtonsoft.Json kullanıyorsanız (veya bu amaçla eklemeye istekliyseniz), Jon Skeet'in şu şekilde yanıtında bahsettiği o korkunç genişletme yöntemini uygulayabilirsiniz :

public static class ObjectExtensions
{
    public static ExpandoObject ToExpando(this object obj)
        => JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(obj));
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.