C # 'động' không thể truy cập các thuộc tính từ các kiểu ẩn danh được khai báo trong một hội đồng khác


87

Mã bên dưới đang hoạt động tốt miễn là tôi có lớp ClassSameAssemblytrong cùng một assembly với lớp Program. Nhưng khi tôi chuyển lớp ClassSameAssemblysang một tập hợp riêng biệt, một RuntimeBinderException(xem bên dưới) sẽ được ném ra. Có thể giải quyết nó không?

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' không chứa định nghĩa cho '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: tại CallSite.Target (Closure, CallSite, Object) tại System.Dynamic.UpdateDelegates.UpdateAndExecute1 [T0, TRet] (CallSite site, T0 arg0) tại ConsoleApplication2.Program.Main (String [] args) trong C: \ temp \ Projects \ ConsoleApplication2 \ ConsoleApplication2 \ Program.cs: dòng 23 tại System.AppDomain._nExecuteAssembly (RuntimeAssembly, String [] args) tại System.AppDomain.nExecuteAssembly (RuntimeAssembly, String [] args) tại System.AppDomain.ExecuteAssembly. String assemblyFile, Evidence assemblySecurity, String [] args)
mehanik

tại Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly () tại System.Threading.ThreadHelper.ThreadStart_Context (Trạng thái đối tượng) tại System.Threading.ExecutionContext.Run (ExecutionContext thực hiệnContext, ContextCallback callback, Object state, Boolean ignoreSy System.Ththreading). ExecutionContext.Run (ExecutionContext thi hànhContext, ContextCallback gọi lại, Trạng thái đối tượng) tại System.Threading.ThreadHelper.ThreadStart () InnerException:
mehanik

bất kỳ giải pháp cuối cùng với mã nguồn đầy đủ?
Kiquenet

Câu trả lời:


116

Tôi tin rằng vấn đề là kiểu ẩn danh được tạo ra internal, vì vậy chất kết dính không thực sự "biết" về nó như vậy.

Hãy thử sử dụng ExpandoObject để thay thế:

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

Tôi biết điều đó có phần xấu xí, nhưng đó là điều tốt nhất tôi có thể nghĩ đến vào lúc này ... Tôi không nghĩ bạn thậm chí có thể sử dụng trình khởi tạo đối tượng với nó, bởi vì trong khi nó được gõ mạnh ExpandoObject, trình biên dịch sẽ không biết phải làm gì với "Tên" và "Tuổi". Bạn thể làm điều này:

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

nhưng điều đó không tốt hơn nhiều ...

Bạn có thể có khả năng viết một phương pháp khuyến nông để chuyển đổi một loại vô danh đến một expando với những nội dung tương tự thông qua phản ánh. Sau đó, bạn có thể viết:

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

Tuy nhiên điều đó khá kinh khủng :(


1
Cảm ơn Jon. Tôi vừa gặp vấn đề tương tự khi sử dụng một lớp đã xảy ra là riêng tư đối với hội đồng.
Dave Markle

2
Tuy nhiên, tôi rất thích một cái gì đó giống như ví dụ khủng khiếp của bạn ở cuối cùng, chỉ là không kinh khủng. Để sử dụng: dynamic props = new {Metadata = DetailModelMetadata.Create, PageTitle = "New Content", PageHeading = "Content Management"}; và có các đạo cụ được đặt tên được thêm vào như các thành viên năng động sẽ rất tuyệt!
ProfK

Giao diện ngẫu hứng của khuôn khổ mã nguồn mở làm được nhiều điều với dlr, nó có cú pháp khởi tạo nội tuyến hoạt động cho bất kỳ đối tượng nào động hoặc tĩnh. return Build<ExpandoObject>.NewObject(Name:"Micheal", Age: 20);
jbtule

1
bất kỳ mẫu mã nguồn đầy đủ nào cho một phương pháp mở rộng để chuyển đổi một loại ẩn danh thành một loại mở rộng?
Kiquenet

1
@ Md.lbrahim: Về cơ bản, bạn không thể. Bạn sẽ phải làm điều đó trên objecthoặc trên một loại chung (bạn có thể yêu cầu đó là một lớp ...) và kiểm tra loại tại thời điểm thực thi.
Jon Skeet,

63

Bạn có thể sử dụng [assembly: InternalsVisibleTo("YourAssemblyName")]để hiển thị các phần bên trong lắp ráp.


2
Câu trả lời của Jon đầy đủ hơn, nhưng điều này thực sự cung cấp một cách giải quyết hợp lý đơn giản cho tôi. Cảm ơn :)
kelloti.

Tôi đã đập đầu hàng giờ trên các diễn đàn khác nhau nhưng không tìm thấy câu trả lời đơn giản nào ngoại trừ câu trả lời này. Cảm ơn Luke. Nhưng tôi vẫn không thể hiểu tại sao một kiểu động lại không thể truy cập được bên ngoài một assembly như nó làm trong cùng một assembly? Ý tôi là tại sao lại có hạn chế này trong .Net.
Faisal Mq

@FaisalMq đó là do trình biên dịch tạo ra các lớp ẩn danh khai báo chúng là "nội bộ". Không biết đâu là lý do thực sự.
ema

2
Vâng, tôi nghĩ câu trả lời này là rất quan trọng, bởi vì tôi không muốn thay đổi mã làm việc, tôi chỉ cần kiểm tra nó từ lắp ráp khác
PandaWood

Một lưu ý cần thêm ở đây là bạn cần khởi động lại Visual Studio sau thay đổi này để điều này hoạt động.
Rady

11

Tôi đã gặp phải sự cố tương tự và muốn thêm vào câu trả lời của Jon Skeets rằng có một tùy chọn khác. Lý do tôi tìm ra là tôi nhận ra rằng nhiều phương thức mở rộng trong Asp MVC3 sử dụng các lớp ẩn danh làm đầu vào để cung cấp các thuộc tính html (new {alt = "Image alt", style = "padding-top: 5px"} =>

Dù sao đi nữa - các hàm đó sử dụng phương thức khởi tạo của lớp RouteValueDictionary. Tôi đã tự mình thử điều đó và chắc chắn rằng nó hoạt động - mặc dù chỉ là cấp độ đầu tiên (tôi đã sử dụng cấu trúc nhiều cấp độ). SO - trong mã này sẽ là:

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

VẬY ... Điều gì đang thực sự xảy ra ở đây? Xem qua bên trong RouteValueDictionary cho thấy mã này (giá trị ~ = o ở trên):

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

VẬY - sử dụng TypeDescriptor.GetProperties (o), chúng ta có thể lấy các thuộc tính và giá trị mặc dù kiểu ẩn danh được xây dựng dưới dạng nội bộ trong một assembly riêng! Và tất nhiên điều này sẽ khá dễ dàng để mở rộng để làm cho nó đệ quy. Và để tạo một phương thức mở rộng nếu bạn muốn.

Hi vọng điêu nay co ich!

/ Victor


Xin lỗi vì sự nhầm lẫn đó. Cập nhật mã từ prop1 => p1 nếu thích hợp. Tuy nhiên - ý tưởng với toàn bộ bài là đặt TypeDescriptor.GetProperties về phía trước như một tùy chọn để giải quyết vấn đề, mà hy vọng là nào rõ ràng ...
Victor

Điều này thực sự ngu ngốc khi động không thể làm điều này cho chúng tôi. Tôi thực sự yêu và thực sự ghét động.
Chris Marisic

2

Đây là phiên bản thô sơ của một phương thức mở rộng cho ToExpandoObject mà tôi chắc chắn có chỗ để đánh bóng.

    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

Một giải pháp sạch hơn sẽ là:

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

Mà bây giờ là một ExpandoObject.

Các bạn nhớ tham khảo:

Microsoft.CSharp.dll

1

Giải pháp dưới đây phù hợp với tôi trong các dự án ứng dụng bảng điều khiển của tôi

Đặt [assembly: InternalsVibleTo ("YourAssemblyName")] trong \ Properties \ AssemblyInfo.cs của dự án riêng biệt với hàm trả về đối tượng động.

"YourAssemblyName" là tên lắp ráp của việc gọi dự án. Bạn có thể nhận được điều đó thông qua Assembly.GetExecutingAssembly (). FullName bằng cách thực thi nó trong gọi dự án.


0

Phương thức mở rộng ToExpando (được đề cập trong câu trả lời của Jon) cho những người dũng cảm

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

Nếu bạn đã sử dụng Newtonsoft.Json trong dự án của mình (hoặc bạn sẵn sàng thêm nó cho mục đích này), bạn có thể triển khai phương pháp mở rộng khủng khiếp mà Jon Skeet đang đề cập đến trong câu trả lời của mình như sau:

public static class ObjectExtensions
{
    public static ExpandoObject ToExpando(this object obj)
        => JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(obj));
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.