Làm thế nào để truy cập thuộc tính của kiểu ẩn danh trong C #?


125

Tôi có cái này:

List<object> nodes = new List<object>(); 

nodes.Add(
new {
    Checked     = false,
    depth       = 1,
    id          = "div_" + d.Id
});

... và tôi tự hỏi liệu sau đó tôi có thể lấy tài sản "Đã kiểm tra" của đối tượng ẩn danh hay không. Tôi không chắc liệu điều này có khả thi hay không. Đã cố gắng làm điều này:

if (nodes.Any(n => n["Checked"] == false)) ... nhưng nó không hoạt động.

Cảm ơn

Câu trả lời:


63

Nếu bạn muốn có một danh sách được đánh máy mạnh mẽ gồm các kiểu ẩn danh, bạn cũng cần đặt danh sách thành một kiểu ẩn danh. Cách dễ nhất để làm điều này là chiếu một chuỗi chẳng hạn như một mảng vào một danh sách, ví dụ:

var nodes = (new[] { new { Checked = false, /* etc */ } }).ToList();

Sau đó, bạn sẽ có thể truy cập nó như:

nodes.Any(n => n.Checked);

Do cách thức hoạt động của trình biên dịch, phần sau cũng sẽ hoạt động khi bạn đã tạo danh sách, vì các kiểu ẩn danh có cấu trúc giống nhau nên chúng cũng là cùng một kiểu. Tôi không có trình biên dịch để xác minh điều này.

nodes.Add(new { Checked = false, /* etc */ });

263

Nếu bạn đang lưu trữ đối tượng dưới dạng kiểu object, bạn cần sử dụng phản chiếu. Điều này đúng với bất kỳ loại đối tượng nào, ẩn danh hay khác. Trên một đối tượng o, bạn có thể lấy kiểu của nó:

Type t = o.GetType();

Sau đó, bạn tra cứu một thuộc tính:

PropertyInfo p = t.GetProperty("Foo");

Sau đó, bạn có thể nhận được một giá trị:

object v = p.GetValue(o, null);

Câu trả lời này đã quá hạn cho một bản cập nhật cho C # 4:

dynamic d = o;
object v = d.Foo;

Và bây giờ là một thay thế khác trong C # 6:

object v = o?.GetType().GetProperty("Foo")?.GetValue(o, null);

Lưu ý rằng bằng cách sử dụng ?.chúng gây ra kết quả vnulltrong ba tình huống khác nhau!

  1. onull, vì vậy không có đối tượng nào cả
  2. okhông phải nullnhưng không có tài sảnFoo
  3. ocó một tài sản Foonhưng giá trị thực của nó xảy ra null.

Vì vậy, điều này không tương đương với các ví dụ trước đó, nhưng có thể có ý nghĩa nếu bạn muốn xử lý cả ba trường hợp như nhau.


4
Chưa bao giờ sử dụng động trước đây cho đến bây giờ, bản cập nhật tốt đẹp cho .NET 4.0
Alan

trong giải pháp c # 4, bạn sẽ nhận được ngoại lệ thời gian chạy nếu thuộc tính không tồn tại ( object v = d.Foo), trong khi GetValue(o, null)sẽ là null nếu không tồn tại.
YaakovHatam

1
Không, GetPropertysẽ trả về nullGetValuesẽ ném nếu vượt qua điều đó null, vì vậy hiệu ứng tổng thể là một ngoại lệ. Phiên bản C # 4.0 đưa ra một ngoại lệ mô tả nhiều hơn.
Daniel Earwicker

4
Nếu bạn đang sử dụng động trong lắp ráp khác với nguồn thì bạn cần phải sử dụng [InternalsVisibleTo]
Sarath

2
@DanielEarwicker cảm ơn vì đã hoàn thành. Nó cũng áp dụng cho các loại ẩn danh. Vì tất cả các thuộc tính được tạo cho các loại ẩn danh là nội bộ.
Sarath

13

Bạn có thể lặp lại các thuộc tính của kiểu ẩn danh bằng Reflection; xem có thuộc tính "Đã kiểm tra" hay không và nếu có thì lấy giá trị của nó.

Xem bài đăng trên blog này: http://blogs.msdn.com/wriju/archive/2007/10/26/c-3-0-anonymous-type-and-net-reflection-hand-in-hand.aspx

Vì vậy, một cái gì đó như:

foreach(object o in nodes)
{
    Type t = o.GetType();

    PropertyInfo[] pi = t.GetProperties(); 

    foreach (PropertyInfo p in pi)
    {
        if (p.Name=="Checked" && !(bool)p.GetValue(o))
            Console.WriteLine("awesome!");
    }
}

6
Nếu bạn chỉ cần một thuộc tính và bạn đã biết tên của nó, thì chẳng ích gì khi xem qua tất cả chúng; chỉ cần sử dụng GetProperty và GetValue. Ngoài ra, System.out.println là Java, C # không ...
Chris Charabaruk

Rất tiếc, đúng là như vậy, Chris! Một chút xấu hổ ... đã sửa xong.
glennkentwell

6

Câu trả lời được chấp nhận mô tả chính xác cách danh sách nên được khai báo và rất được khuyến khích cho hầu hết các trường hợp.

Nhưng tôi đã gặp một kịch bản khác, cũng bao hàm câu hỏi được đặt ra. Điều gì sẽ xảy ra nếu bạn phải sử dụng danh sách đối tượng hiện có, như ViewData["htmlAttributes"]trong MVC ? Làm thế nào bạn có thể truy cập các thuộc tính của nó (chúng thường được tạo thông qua new { @style="width: 100px", ... })?

Đối với kịch bản hơi khác này, tôi muốn chia sẻ với bạn những gì tôi đã tìm ra. Trong các giải pháp bên dưới, tôi giả sử khai báo sau cho nodes:

List<object> nodes = new List<object>();

nodes.Add(
new
{
    Checked = false,
    depth = 1,
    id = "div_1" 
});

1. Giải pháp với động

Trong C # 4.0 và các phiên bản cao hơn , bạn có thể chỉ cần truyền sang động và viết:

if (nodes.Any(n => ((dynamic)n).Checked == false))
    Console.WriteLine("found not checked element!");

Lưu ý: Điều này đang sử dụng liên kết muộn, có nghĩa là nó sẽ chỉ nhận ra trong thời gian chạy nếu đối tượng không có thuộc Checkedtính và ném một RuntimeBinderExceptiontrong trường hợp này - vì vậy nếu bạn cố gắng sử dụng thuộc tính không tồn tại, Checked2bạn sẽ nhận được thông báo sau tại runtime: "'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'" .

2. Giải pháp có phản ánh

Giải pháp với sự phản chiếu hoạt động cả với các phiên bản trình biên dịch C # cũ và mới . Đối với các phiên bản C # cũ, vui lòng xem gợi ý ở cuối câu trả lời này.

Lý lịch

Như một điểm khởi đầu, tôi đã tìm thấy một câu trả lời tốt ở đây . Ý tưởng là chuyển đổi kiểu dữ liệu ẩn danh thành từ điển bằng cách sử dụng phản xạ. Từ điển giúp bạn dễ dàng truy cập các thuộc tính, vì tên của chúng được lưu trữ dưới dạng khóa (bạn có thể truy cập chúng như vậy myDict["myProperty"]).

Lấy cảm hứng từ mã kiểm tra vào liên kết ở trên, tôi tạo ra một lớp mở rộng cung cấp GetProp, UnanonymizePropertiesUnanonymizeListItemsnhư phương pháp khuyến nông, đó truy cập đơn giản hóa để tính nặc danh. Với lớp này, bạn chỉ cần thực hiện truy vấn như sau:

if (nodes.UnanonymizeListItems().Any(n => (bool)n["Checked"] == false))
{
    Console.WriteLine("found not checked element!");
}

hoặc bạn có thể sử dụng biểu thức nodes.UnanonymizeListItems(x => (bool)x["Checked"] == false).Any()làm ifđiều kiện để lọc ngầm và sau đó kiểm tra xem có bất kỳ phần tử nào được trả về hay không.

Để lấy đối tượng đầu tiên chứa thuộc tính "Đã kiểm tra" và trả về thuộc tính "chiều sâu" của nó, bạn có thể sử dụng:

var depth = nodes.UnanonymizeListItems()
             ?.FirstOrDefault(n => n.Contains("Checked")).GetProp("depth");

hoặc ngắn hơn: nodes.UnanonymizeListItems()?.FirstOrDefault(n => n.Contains("Checked"))?["depth"];

Lưu ý: Nếu bạn có danh sách các đối tượng không nhất thiết phải chứa tất cả các thuộc tính (ví dụ: một số không chứa thuộc tính "Đã kiểm tra") và bạn vẫn muốn tạo truy vấn dựa trên các giá trị "Đã kiểm tra", bạn có thể làm cái này:

if (nodes.UnanonymizeListItems(x => { var y = ((bool?)x.GetProp("Checked", true)); 
                                      return y.HasValue && y.Value == false;}).Any())
{
    Console.WriteLine("found not checked element!");
}

Điều này ngăn cản, điều đó KeyNotFoundExceptionxảy ra nếu thuộc tính "Đã kiểm tra" không tồn tại.


Lớp bên dưới chứa các phương thức mở rộng sau:

  • UnanonymizeProperties: Được sử dụng để khử ẩn danh các thuộc tính có trong một đối tượng. Phương pháp này sử dụng phản xạ. Nó chuyển đổi đối tượng thành một từ điển chứa các thuộc tính và giá trị của nó.
  • UnanonymizeListItems: Dùng để chuyển một danh sách các đối tượng thành danh sách các từ điển chứa các thuộc tính. Nó có thể chứa một biểu thức lambda để lọc trước.
  • GetProp: Được sử dụng để trả về một giá trị duy nhất phù hợp với tên thuộc tính đã cho. Cho phép xử lý các thuộc tính không tồn tại dưới dạng giá trị null (true) chứ không phải là KeyNotFoundException (false)

Đối với các ví dụ ở trên, tất cả những gì bắt buộc là bạn thêm lớp mở rộng bên dưới:

public static class AnonymousTypeExtensions
{
    // makes properties of object accessible 
    public static IDictionary UnanonymizeProperties(this object obj)
    {
        Type type = obj?.GetType();
        var properties = type?.GetProperties()
               ?.Select(n => n.Name)
               ?.ToDictionary(k => k, k => type.GetProperty(k).GetValue(obj, null));
        return properties;
    }

    // converts object list into list of properties that meet the filterCriteria
    public static List<IDictionary> UnanonymizeListItems(this List<object> objectList, 
                    Func<IDictionary<string, object>, bool> filterCriteria=default)
    {
        var accessibleList = new List<IDictionary>();
        foreach (object obj in objectList)
        {
            var props = obj.UnanonymizeProperties();
            if (filterCriteria == default
               || filterCriteria((IDictionary<string, object>)props) == true)
            { accessibleList.Add(props); }
        }
        return accessibleList;
    }

    // returns specific property, i.e. obj.GetProp(propertyName)
    // requires prior usage of AccessListItems and selection of one element, because
    // object needs to be a IDictionary<string, object>
    public static object GetProp(this object obj, string propertyName, 
                                 bool treatNotFoundAsNull = false)
    {
        try 
        {
            return ((System.Collections.Generic.IDictionary<string, object>)obj)
                   ?[propertyName];
        }
        catch (KeyNotFoundException)
        {
            if (treatNotFoundAsNull) return default(object); else throw;
        }
    }
}

Gợi ý: Đoạn mã trên được sử dụng null-có điều kiện khai thác, có sẵn từ C # phiên bản 6.0 - nếu bạn đang làm việc với các trình biên dịch C # cũ (ví dụ như C # 3.0), chỉ đơn giản là thay thế ?.bằng .?[bởi [ở khắp mọi nơi, ví dụ như

var depth = nodes.UnanonymizeListItems()
            .FirstOrDefault(n => n.Contains("Checked"))["depth"];

Nếu bạn không buộc phải sử dụng trình biên dịch C # cũ hơn, hãy giữ nguyên nó, bởi vì sử dụng null-điều kiện làm cho việc xử lý null dễ dàng hơn nhiều.

Lưu ý: Giống như các giải pháp khác với động, giải pháp này cũng đang sử dụng liên kết trễ, nhưng trong trường hợp này, bạn không gặp phải ngoại lệ - nó chỉ đơn giản là sẽ không tìm thấy phần tử nếu bạn đang đề cập đến thuộc tính không tồn tại, miễn là khi bạn giữ các toán tử điều kiện null .

Điều có thể hữu ích cho một số ứng dụng là thuộc tính được tham chiếu qua một chuỗi trong giải pháp 2, do đó nó có thể được tham số hóa.


1

Gần đây, tôi đã gặp sự cố tương tự trong .NET 3.5 (không có sẵn động). Đây là cách tôi giải quyết:

// pass anonymous object as argument
var args = new { Title = "Find", Type = typeof(FindCondition) };

using (frmFind f = new frmFind(args)) 
{
...
...
}

Được điều chỉnh từ một nơi nào đó trên stackoverflow:

// Use a custom cast extension
public static T CastTo<T>(this Object x, T targetType)
{
   return (T)x;
}

Bây giờ lấy lại đối tượng thông qua ép kiểu:

public partial class frmFind: Form
{
    public frmFind(object arguments)
    {

        InitializeComponent();

        var args = arguments.CastTo(new { Title = "", Type = typeof(Nullable) });

        this.Text = args.Title;

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