C # 'ไดนามิก' ไม่สามารถเข้าถึงคุณสมบัติจากชนิดที่ไม่ระบุชื่อที่ประกาศในแอสเซมบลีอื่น


87

โค้ดด้านล่างนี้ทำงานได้ดีตราบใดที่ฉันได้เรียนในการชุมนุมเช่นเดียวกับระดับClassSameAssembly Programแต่เมื่อฉันย้ายชั้นเรียนClassSameAssemblyไปยังชุดประกอบแยกต่างหากRuntimeBinderException(ดูด้านล่าง) จะถูกโยนทิ้ง สามารถแก้ไขได้หรือไม่?

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' ไม่มีคำจำกัดความสำหรับ '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

StackTrace: ที่ CallSite.Target (Closure, CallSite, Object) ที่ System.Dynamic.UpdateDelegates.UpdateAndExecute1 [T0, TRet] (ไซต์ CallSite, T0 arg0) ที่ ConsoleApplication2.Program.Main (String [] args) ใน C: \ temp \ Projects \ ConsoleApplication2 \ ConsoleApplication2 \ Program.cs: บรรทัดที่ 23 ที่ System.AppDomain._nExecuteAssembly (RuntimeAssembly แอสเซมบลีสตริง [] args) ที่ System.AppDomain.nExecuteAssembly (RuntimeAssembly แอสเซมบลีสตริง [] args) ที่ System.AppDomain.ExecuteAssembly String assemblyFile, Evidence assemblySecurity, String [] args)
mehanik

ที่ Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly () ที่ System.Threading.ThreadHelper.ThreadStart_Context (สถานะวัตถุ) ที่ System.Threading.ExecutionContext.Run (ExecutionContextecutionContext การเรียกกลับ ContextCallback สถานะวัตถุบูลีนละเว้นการซิงค์ Ctx) ที่ System.Threading ExecutionContext.Run (ExecutionContext ExecutionContext, ContextCallback callback, Object state) ที่ System.Threading.ThreadHelper.ThreadStart () InnerException:
mehanik

โซลูชันสุดท้ายที่มีซอร์สโค้ดแบบเต็มหรือไม่
Kiquenet

คำตอบ:


116

ฉันเชื่อว่าปัญหาคือประเภทที่ไม่ระบุตัวตนถูกสร้างขึ้นinternalดังนั้นตัวประสานจึงไม่ "รู้" เกี่ยวกับเรื่องนี้จริงๆ

ลองใช้ ExpandoObject แทน:

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

ฉันรู้ว่ามันค่อนข้างน่าเกลียด แต่มันดีที่สุดที่ฉันคิดได้ในตอนนี้ ... ฉันไม่คิดว่าคุณจะใช้ object initializer กับมันได้ด้วยซ้ำเพราะในขณะที่มันถูกพิมพ์อย่างแรงเนื่องจากExpandoObjectคอมไพเลอร์ไม่รู้ว่าต้องทำอย่างไร ด้วย "ชื่อ" และ "อายุ" คุณอาจจะสามารถที่จะทำเช่นนี้:

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

แต่ก็ไม่ดีขึ้นเท่าไหร่ ...

คุณสามารถอาจเขียนวิธีขยายการแปลงชนิดที่ไม่ระบุชื่อไปยัง Expando กับเนื้อหาเดียวกันผ่านการสะท้อน จากนั้นคุณสามารถเขียน:

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

น่ากลัวทีเดียว :(


1
ขอบคุณจอน ฉันเพิ่งมีปัญหาเดียวกันกับการใช้คลาสซึ่งเป็นเรื่องส่วนตัวสำหรับการชุมนุม
Dave Markle

2
ฉันจะชอบบางอย่างเช่นตัวอย่างที่น่ากลัวของคุณในตอนท้าย แต่ก็ไม่น่ากลัวเท่า วิธีใช้: dynamic props = new {Metadata = DetailModelMetadata.Create, PageTitle = "New Content", PageHeading = "Content Management"}; และเพิ่มอุปกรณ์ประกอบฉากที่มีชื่อเป็นสมาชิกแบบไดนามิกจะดีมาก!
ProfK

อินเทอร์เฟซแบบทันควันของเฟรมเวิร์กโอเพนซอร์สทำอะไรได้มากมายกับ dlr มันมีไวยากรณ์การเริ่มต้นแบบอินไลน์ที่ใช้ได้กับวัตถุใด ๆ แบบไดนามิกหรือคง return Build<ExpandoObject>.NewObject(Name:"Micheal", Age: 20);
jbtule

1
ตัวอย่างซอร์สโค้ดแบบเต็มสำหรับวิธีการขยายเพื่อแปลงประเภทที่ไม่ระบุชื่อเป็น expando
Kiquenet

1
@ Md.lbrahim: โดยพื้นฐานแล้วคุณทำไม่ได้ คุณต้องทำไม่ว่าจะในobjectหรือประเภททั่วไป (คุณสามารถกำหนดให้เป็นคลาส ... ) และตรวจสอบประเภทในขณะดำเนินการ
Jon Skeet

63

คุณสามารถใช้[assembly: InternalsVisibleTo("YourAssemblyName")]เพื่อทำให้การประกอบของคุณมองเห็นได้ภายใน


2
คำตอบของจอนนั้นสมบูรณ์กว่า แต่นี่เป็นวิธีแก้ปัญหาที่ง่ายพอสมควรสำหรับฉัน ขอบคุณ :)
kelloti

ฉันกระแทกหัวเป็นเวลาหลายชั่วโมงในฟอรัมต่างๆ แต่ไม่พบคำตอบง่ายๆยกเว้นอันนี้ ขอบคุณลุค แต่ฉันยังไม่เข้าใจว่าทำไมประเภทไดนามิกถึงไม่สามารถเข้าถึงได้นอกแอสเซมบลีเหมือนในแอสเซมบลีเดียวกัน ฉันหมายถึงทำไมข้อ จำกัด นี้ใน. Net
Faisal Mq

@FaisalMq เป็นเพราะคอมไพเลอร์ที่สร้างคลาสที่ไม่ระบุตัวตนประกาศว่า "ภายใน" ไม่รู้ว่าอันไหนคือเหตุผลที่แท้จริง
ema

2
ใช่ฉันคิดว่าคำตอบนี้สำคัญเพราะฉันไม่ต้องการเปลี่ยนรหัสการทำงานฉันแค่ต้องทดสอบจากแอสเซมบลีอื่น
PandaWood

หมายเหตุประการหนึ่งที่ต้องเพิ่มที่นี่คือคุณต้องรีสตาร์ท Visual Studio หลังจากการเปลี่ยนแปลงนี้จึงจะทำงานได้
Rady

11

ฉันพบปัญหาเกี่ยวกับ similair และต้องการเพิ่มคำตอบให้กับ Jon Skeets ว่ามีอีกทางเลือกหนึ่ง เหตุผลที่ฉันพบก็คือฉันตระหนักว่าวิธีการขยายจำนวนมากใน Asp MVC3 ใช้คลาสที่ไม่ระบุชื่อเป็นอินพุตเพื่อให้แอตทริบิวต์ html (ใหม่ {alt = "Image alt", style = "padding-top: 5px"} =>

อย่างไรก็ตาม - ฟังก์ชันเหล่านั้นใช้ตัวสร้างของคลาส RouteValueDictionary ฉันลองทำด้วยตัวเองแล้วและมันก็ใช้ได้ - แม้ว่าจะเป็นเพียงระดับแรกเท่านั้น (ฉันใช้โครงสร้างหลายระดับ) ดังนั้น - ในรหัสนี้จะเป็น:

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

ดังนั้น ... เกิดอะไรขึ้นที่นี่? ดูภายใน RouteValueDictionary แสดงรหัสนี้ (ค่า ~ = o ด้านบน):

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

ดังนั้น - การใช้ TypeDescriptor.GetProperties (o) เราจะสามารถรับคุณสมบัติและค่าได้แม้จะมีการสร้างประเภทที่ไม่ระบุชื่อเป็นภายในในแอสเซมบลีแยกต่างหาก! และแน่นอนว่านี่จะเป็นเรื่องง่ายมากที่จะขยายเพื่อทำให้เกิดซ้ำ และเพื่อสร้างวิธีการขยายหากคุณต้องการ

หวังว่านี่จะช่วยได้!

/ วิคเตอร์


ขออภัยในความสับสน รหัสปรับปรุงจาก prop1 => p1 ตามความเหมาะสม ยังคง - แนวคิดในโพสต์ทั้งหมดคือการหยิบยก TypeDescriptor.GetProperties มาเป็นตัวเลือกในการแก้ปัญหาซึ่งหวังว่าจะชัดเจนอยู่ดี ...
วิกเตอร์

นี่มันโง่จริงๆที่ไดนามิกไม่สามารถทำสิ่งนี้ให้เราได้ ฉันทั้งรักและเกลียดไดนามิกจริงๆ
Chris Marisic

2

นี่คือเวอร์ชันพื้นฐานของวิธีการขยายสำหรับ ToExpandoObject ที่ฉันแน่ใจว่ามีที่ว่างสำหรับการขัด

    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

ทางออกที่สะอาดกว่าคือ:

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

ซึ่งตอนนี้เป็น ExpandoObject

อย่าลืมอ้างอิง:

Microsoft.CSharp.dll

1

โซลูชันด้านล่างใช้ได้ผลกับฉันในโครงการแอปพลิเคชันคอนโซลของฉัน

ใส่ [แอสเซมบลี: InternalsVisibleTo ("YourAssemblyName")] ใน \ Properties \ AssemblyInfo.cs ของโปรเจ็กต์แยกที่มีฟังก์ชันที่ส่งคืนวัตถุไดนามิก

"YourAssemblyName" คือชื่อแอสเซมบลีของการเรียกโปรเจ็กต์ คุณสามารถรับได้ผ่าน Assembly.GetExecutingAssembly () FullName โดยดำเนินการในการเรียกโปรเจ็กต์


0

วิธีการขยาย ToExpando (กล่าวถึงในคำตอบของจอน) สำหรับผู้กล้า

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

หากคุณใช้ Newtonsoft.Json ในโครงการของคุณอยู่แล้ว (หรือคุณต้องการเพิ่มเพื่อจุดประสงค์นี้) คุณสามารถใช้วิธีการขยายที่น่ากลัวที่ Jon Skeet อ้างถึงในคำตอบของเขาเช่นนี้:

public static class ObjectExtensions
{
    public static ExpandoObject ToExpando(this object obj)
        => JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(obj));
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.