GetProperties () để trả về tất cả các thuộc tính cho hệ thống phân cấp kế thừa giao diện


96

Giả sử hệ thống phân cấp kế thừa giả định sau:

public interface IA
{
  int ID { get; set; }
}

public interface IB : IA
{
  string Name { get; set; }
}

Sử dụng phản xạ và thực hiện cuộc gọi sau:

typeof(IB).GetProperties(BindingFlags.Public | BindingFlags.Instance) 

sẽ chỉ mang lại các thuộc tính của giao diện IB, là " Name".

Nếu chúng tôi thực hiện một thử nghiệm tương tự trên đoạn mã sau,

public abstract class A
{
  public int ID { get; set; }
}

public class B : A
{
  public string Name { get; set; }
}

lệnh gọi typeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance)sẽ trả về một mảng PropertyInfođối tượng cho " ID" và " Name".

Có cách nào dễ dàng để tìm tất cả các thuộc tính trong hệ thống phân cấp kế thừa cho các giao diện như trong ví dụ đầu tiên không?

Câu trả lời:


112

Tôi đã chỉnh sửa mã ví dụ của @Marc Gravel thành một phương thức mở rộng hữu ích đóng gói cả các lớp và giao diện. Nó cũng bổ sung các thuộc tính giao diện trước tiên mà tôi tin là hành vi mong đợi.

public static PropertyInfo[] GetPublicProperties(this Type type)
{
    if (type.IsInterface)
    {
        var propertyInfos = new List<PropertyInfo>();

        var considered = new List<Type>();
        var queue = new Queue<Type>();
        considered.Add(type);
        queue.Enqueue(type);
        while (queue.Count > 0)
        {
            var subType = queue.Dequeue();
            foreach (var subInterface in subType.GetInterfaces())
            {
                if (considered.Contains(subInterface)) continue;

                considered.Add(subInterface);
                queue.Enqueue(subInterface);
            }

            var typeProperties = subType.GetProperties(
                BindingFlags.FlattenHierarchy 
                | BindingFlags.Public 
                | BindingFlags.Instance);

            var newPropertyInfos = typeProperties
                .Where(x => !propertyInfos.Contains(x));

            propertyInfos.InsertRange(0, newPropertyInfos);
        }

        return propertyInfos.ToArray();
    }

    return type.GetProperties(BindingFlags.FlattenHierarchy
        | BindingFlags.Public | BindingFlags.Instance);
}

2
Pure Brilliance! Cảm ơn bạn, điều này đã giải quyết được một vấn đề mà tôi gặp phải tương tự như câu hỏi của op.
kamui

1
Các tham chiếu của bạn đến BindingFlags.FlattenHierarchy là thừa khi bạn cũng đang sử dụng BindingFlags.Instance.
Chris Ward

1
Tôi đã thực hiện điều này nhưng với một Stack<Type>thay vì a Queue<>. Với một chồng, các tổ tiên duy trì một trật tự như vậy interface IFoo : IBar, IBazở đâu IBar : IBubblevà 'IBaz: IFlubber , the order of reflection becomes: Ibar , IBubble , IBaz , IFlubber , IFoo`.
IAbstract

4
Không cần đệ quy hoặc hàng đợi vì GetInterfaces () đã trả về tất cả các giao diện được thực hiện bởi một kiểu. Như Marc đã lưu ý, không có hệ thống phân cấp, vậy tại sao chúng ta phải "đệ quy" bất cứ điều gì?
glopes

3
@FrankyHollywood đó là lý do tại sao bạn không sử dụng GetProperties. Bạn sử dụng GetInterfacestrên kiểu bắt đầu của mình sẽ trả về danh sách phẳng của tất cả các giao diện và chỉ cần thực hiện GetPropertiestrên mỗi giao diện. Không cần đệ quy. Không có kiểu kế thừa hoặc kiểu cơ sở trong giao diện.
glopes

77

Type.GetInterfaces trả về cấu trúc phân cấp phẳng, vì vậy không cần thiết phải truy xuất đệ quy.

Toàn bộ phương pháp có thể được viết ngắn gọn hơn nhiều bằng cách sử dụng LINQ:

public static IEnumerable<PropertyInfo> GetPublicProperties(this Type type)
{
    if (!type.IsInterface)
        return type.GetProperties();

    return (new Type[] { type })
           .Concat(type.GetInterfaces())
           .SelectMany(i => i.GetProperties());
}

8
Đây chắc chắn phải là câu trả lời đúng! Không cần đệ quy phức tạp.
glopes

Câu trả lời chắc chắn cảm ơn bạn. Làm thế nào chúng ta có thể nhận được giá trị của một thuộc tính trong giao diện cơ sở?
ilker unal

1
@ilkerunal: Cách thông thường: Gọi GetValueđối tượng được truy xuất PropertyInfo, chuyển thể hiện của bạn (có giá trị thuộc tính cần lấy) làm tham số. Ví dụ: var list = new[] { 'a', 'b', 'c' }; var count = typeof(IList).GetPublicProperties().First(i => i.Name == "Count").GetValue(list);← sẽ trả về 3, mặc dù Countđược định nghĩa bên trong ICollection, không phải IList.
Douglas

2
Giải pháp này có sai sót ở chỗ nó có thể trả về các thuộc tính cùng tên nhiều lần. Cần làm sạch thêm các kết quả để có một danh sách tài sản riêng biệt. Câu trả lời được chấp nhận là giải pháp chính xác hơn vì nó đảm bảo trả về các thuộc tính có tên duy nhất và làm như vậy bằng cách lấy tên gần nhất trong chuỗi kế thừa.
dùng3524983

1
@AntWaters GetInterfaces là không bắt buộc nếu typelà một lớp, bởi vì lớp cụ thể PHẢI triển khai tất cả các thuộc tính được xác định trong tất cả các Giao diện trong chuỗi kế thừa. Sử dụng GetInterfacestrong trường hợp đó sẽ dẫn đến việc TẤT CẢ các thuộc tính bị trùng lặp.
Chris Schaller

15

Phân cấp giao diện là một vấn đề - chúng không thực sự "kế thừa" như vậy, vì bạn có thể có nhiều "cha mẹ" (nếu muốn có một thuật ngữ tốt hơn).

"Làm phẳng" (một lần nữa, không phải là thuật ngữ hoàn toàn phù hợp) hệ thống phân cấp có thể liên quan đến việc kiểm tra tất cả các giao diện mà giao diện triển khai và hoạt động từ đó ...

interface ILow { void Low();}
interface IFoo : ILow { void Foo();}
interface IBar { void Bar();}
interface ITest : IFoo, IBar { void Test();}

static class Program
{
    static void Main()
    {
        List<Type> considered = new List<Type>();
        Queue<Type> queue = new Queue<Type>();
        considered.Add(typeof(ITest));
        queue.Enqueue(typeof(ITest));
        while (queue.Count > 0)
        {
            Type type = queue.Dequeue();
            Console.WriteLine("Considering " + type.Name);
            foreach (Type tmp in type.GetInterfaces())
            {
                if (!considered.Contains(tmp))
                {
                    considered.Add(tmp);
                    queue.Enqueue(tmp);
                }
            }
            foreach (var member in type.GetMembers())
            {
                Console.WriteLine(member.Name);
            }
        }
    }
}

7
Tôi không đồng ý. Với tất cả sự tôn trọng dành cho Marc, câu trả lời này cũng không nhận ra rằng GetInterfaces () đã trả về tất cả các giao diện được triển khai cho một kiểu. Chính vì không có "hệ thống phân cấp" nên không cần đệ quy hoặc hàng đợi.
glopes

3

Chính xác vấn đề tương tự có một cách giải quyết được mô tả ở đây .

FlattenHierarchy không hoạt động btw. (chỉ trên các vars tĩnh. nói như vậy trong intellisense)

Cách giải quyết. Cẩn thận với các bản sao.

PropertyInfo[] pis = typeof(IB).GetProperties(BindingFlags.Public | BindingFlags.Instance);
Type[] tt = typeof(IB).GetInterfaces();
PropertyInfo[] pis2 = tt[0].GetProperties(BindingFlags.Public | BindingFlags.Instance);

2

Trả lời @douglas và @ user3524983, phần sau sẽ trả lời câu hỏi của OP:

    static public IEnumerable<PropertyInfo> GetPropertiesAndInterfaceProperties(this Type type, BindingFlags bindingAttr = BindingFlags.Public | BindingFlags.Instance)
    {
        if (!type.IsInterface) {
            return type.GetProperties( bindingAttr);
        }

        return type.GetInterfaces().Union(new Type[] { type }).SelectMany(i => i.GetProperties(bindingAttr)).Distinct();
    }

hoặc, đối với một tài sản riêng lẻ:

    static public PropertyInfo GetPropertyOrInterfaceProperty(this Type type, string propertyName, BindingFlags bindingAttr = BindingFlags.Public|BindingFlags.Instance)
    {
        if (!type.IsInterface) {
            return type.GetProperty(propertyName, bindingAttr);
        }

        return type.GetInterfaces().Union(new Type[] { type }).Select(i => i.GetProperty( propertyName, bindingAttr)).Distinct().Where(propertyInfo => propertyInfo != null).Single();
    }

OK lần sau tôi sẽ gỡ lỗi nó trước khi đăng thay vì sau :-)


1

điều này hoạt động độc đáo và ngắn gọn đối với tôi trong một chất kết dính mô hình MVC tùy chỉnh. Tuy nhiên, sẽ có thể ngoại suy cho bất kỳ kịch bản phản chiếu nào. Vẫn còn mùi hôi mà nó quá trôi

    var props =  bindingContext.ModelType.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance).ToList();

    bindingContext.ModelType.GetInterfaces()
                      .ToList()
                      .ForEach(i => props.AddRange(i.GetProperties()));

    foreach (var property in props)
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.