Làm thế nào liệt kê tất cả các lớp với thuộc tính lớp tùy chỉnh?


151

Câu hỏi dựa trên ví dụ MSDN .

Giả sử chúng ta có một số lớp C # với HelpAttribution trong ứng dụng máy tính để bàn độc lập. Có thể liệt kê tất cả các lớp với thuộc tính như vậy? Liệu nó có ý nghĩa để nhận ra các lớp học theo cách này? Thuộc tính tùy chỉnh sẽ được sử dụng để liệt kê các tùy chọn menu có thể, chọn mục sẽ đưa đến phiên bản màn hình của lớp đó. Số lượng lớp học / vật phẩm sẽ tăng chậm, nhưng theo cách này chúng ta có thể tránh việc liệt kê tất cả chúng ở nơi khác, tôi nghĩ vậy.

Câu trả lời:


205

Chắc chắn rồi. Sử dụng sự phản chiếu:

static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly) {
    foreach(Type type in assembly.GetTypes()) {
        if (type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0) {
            yield return type;
        }
    }
}

7
Đồng ý, nhưng trong trường hợp này, chúng tôi có thể thực hiện khai báo theo giải pháp của casperOne. Thật tuyệt khi có thể sử dụng năng suất, nó thậm chí còn đẹp hơn không phải vậy :)
Jon Skeet

9
Tôi thích LINQ. Yêu nó, thực sự. Nhưng nó phụ thuộc vào .NET 3.5, mang lại lợi nhuận không. Ngoài ra, LINQ cuối cùng cũng bị phá vỡ về cơ bản giống như lợi nhuận. Vì vậy, những gì bạn đã đạt được? Một cú pháp C # cụ thể, đó là một ưu tiên.
Andrew Arnott

1
@AndrewArnott Các dòng mã ngắn nhất và ngắn nhất không liên quan đến hiệu suất, chúng chỉ là những người đóng góp khả thi cho khả năng đọc và bảo trì. Tôi thách thức tuyên bố rằng họ phân bổ ít đối tượng nhất và hiệu suất sẽ nhanh hơn (đặc biệt là không có bằng chứng thực nghiệm); về cơ bản bạn đã viết Selectphương thức mở rộng và trình biên dịch sẽ tạo ra một máy trạng thái giống như khi bạn gọi Selectvì bạn sử dụng yield return. Cuối cùng, bất kỳ lợi ích hiệu suất nào thể đạt được trong phần lớn các trường hợp là tối ưu hóa vi mô.
casperOne

1
Hoàn toàn đúng, @casperOne. Một sự khác biệt rất nhỏ, đặc biệt là so với trọng lượng của chính sự phản chiếu. Có lẽ sẽ không bao giờ đi lên trong một dấu vết hoàn hảo.
Andrew Arnott

1
Tất nhiên, Resharper nói rằng "vòng lặp foreach có thể được chuyển đổi thành biểu thức LINQ" trông giống như sau: assembly.GetTypes (). Trong đó (type => type.GetCustomAttribution (typeof (HelpAttribution), true) .Lipse> 0);
David Barrow

107

Chà, bạn sẽ phải liệt kê tất cả các lớp trong tất cả các hội đồng được tải vào miền ứng dụng hiện tại. Để làm điều đó, bạn sẽ gọi GetAssembliesphương thức trên AppDomainví dụ cho miền ứng dụng hiện tại.

Từ đó, bạn sẽ gọi GetExportedTypes(nếu bạn chỉ muốn các loại công khai) hoặc GetTypestrên mỗi loại Assemblyđể có được các loại có trong hội đồng.

Sau đó, bạn sẽ gọi GetCustomAttributesphương thức mở rộng trên mỗi Typephiên bản, chuyển loại thuộc tính bạn muốn tìm.

Bạn có thể sử dụng LINQ để đơn giản hóa việc này cho bạn:

var typesWithMyAttribute =
    from a in AppDomain.CurrentDomain.GetAssemblies()
    from t in a.GetTypes()
    let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

Truy vấn trên sẽ giúp bạn từng loại với thuộc tính của bạn được áp dụng cho nó, cùng với thể hiện của (các) thuộc tính được gán cho nó.

Lưu ý rằng nếu bạn có một số lượng lớn các hội đồng được tải vào miền ứng dụng của bạn, thì thao tác đó có thể tốn kém. Bạn có thể sử dụng Parallel LINQ để giảm thời gian hoạt động, như vậy:

var typesWithMyAttribute =
    // Note the AsParallel here, this will parallelize everything after.
    from a in AppDomain.CurrentDomain.GetAssemblies().AsParallel()
    from t in a.GetTypes()
    let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

Lọc nó trên một cụ thể Assemblylà đơn giản:

Assembly assembly = ...;

var typesWithMyAttribute =
    from t in assembly.GetTypes()
    let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

Và nếu hội đồng có số lượng lớn các loại trong đó, thì bạn có thể sử dụng lại Parallel LINQ:

Assembly assembly = ...;

var typesWithMyAttribute =
    // Partition on the type list initially.
    from t in assembly.GetTypes().AsParallel()
    let attributes = t.GetCustomAttributes(typeof(HelpAttribute), true)
    where attributes != null && attributes.Length > 0
    select new { Type = t, Attributes = attributes.Cast<HelpAttribute>() };

1
Việc liệt kê tất cả các loại trong tất cả các hội đồng được tải sẽ rất chậm và không giúp bạn kiếm được nhiều tiền. Nó cũng có khả năng rủi ro bảo mật. Bạn có thể dự đoán những hội đồng nào sẽ chứa các loại bạn quan tâm. Chỉ cần liệt kê các loại trong đó.
Andrew Arnott

@Andrew Arnott: Đúng, nhưng đây là những gì được yêu cầu. Nó đủ dễ dàng để cắt xén truy vấn cho một hội đồng cụ thể. Điều này cũng có thêm lợi ích là cung cấp cho bạn ánh xạ giữa loại và thuộc tính.
casperOne

1
Bạn có thể sử dụng cùng một mã trên phiên bản hiện tại với System.Reflection.Assugging.GetExecutingAssugging ()
Chris Moschini

@ChrisMoschini Có, bạn có thể, nhưng không phải lúc nào bạn cũng muốn quét phần lắp ráp hiện tại. Tốt hơn là để nó mở.
casperOne

Tôi đã làm điều này nhiều lần và không có nhiều cách để làm cho nó hiệu quả. Bạn có thể bỏ qua các hội đồng microsoft (chúng được ký với cùng một khóa, vì vậy chúng khá dễ tránh sử dụng hội nghị. Bạn có thể lưu trữ các kết quả trong một tĩnh, duy nhất cho AppDomain mà các hội đồng được tải (phải lưu trữ đầy đủ Tên của các hội đồng mà bạn đã kiểm tra trong trường hợp những người khác được tải trong thời gian đó). Tôi thấy mình ở đây khi tôi đang điều tra các trường hợp được tải bộ đệm của một loại thuộc tính trong thuộc tính. Không chắc chắn về mẫu đó, không chắc chắn khi chúng được khởi tạo, v.v.

34

Câu trả lời khác tham khảo GetCustomAttribut . Thêm cái này làm ví dụ về việc sử dụng IsDefined

Assembly assembly = ...
var typesWithHelpAttribute = 
        from type in assembly.GetTypes()
        where type.IsDefined(typeof(HelpAttribute), false)
        select type;

3
Tôi tin rằng nó là giải pháp thích hợp sử dụng phương pháp dự định khung.
Alexey Omelchenko

11

Như đã nêu, phản ánh là con đường để đi. Nếu bạn định gọi nó thường xuyên, tôi khuyên bạn nên lưu trữ kết quả, vì sự phản chiếu, đặc biệt là liệt kê qua mỗi lớp, có thể khá chậm.

Đây là một đoạn mã của tôi chạy qua tất cả các loại trong tất cả các cụm được tải:

// this is making the assumption that all assemblies we need are already loaded.
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) 
{
    foreach (Type type in assembly.GetTypes())
    {
        var attribs = type.GetCustomAttributes(typeof(MyCustomAttribute), false);
        if (attribs != null && attribs.Length > 0)
        {
            // add to a cache.
        }
    }
}

9

Đây là một cải tiến hiệu suất trên đầu trang của giải pháp được chấp nhận. Lặp lại mặc dù tất cả các lớp có thể chậm vì có rất nhiều. Đôi khi bạn có thể lọc ra toàn bộ lắp ráp mà không cần nhìn vào bất kỳ loại nào.

Ví dụ: nếu bạn đang tìm kiếm một thuộc tính mà bạn tự khai báo, bạn không mong đợi bất kỳ tệp DLL hệ thống nào có chứa bất kỳ loại nào với thuộc tính đó. Thuộc tính Association.GlobalAss lanhCache là một cách nhanh chóng để kiểm tra các DLL hệ thống. Khi tôi thử điều này trên một chương trình thực tế, tôi thấy tôi có thể bỏ qua 30.101 loại và tôi chỉ phải kiểm tra 1.983 loại.

Một cách khác để lọc là sử dụng hội.ReferencedAssemblies. Có lẽ nếu bạn muốn các lớp có thuộc tính cụ thể và thuộc tính đó được xác định trong một hội đồng cụ thể, thì bạn chỉ quan tâm đến tập hợp đó và các tập hợp khác tham chiếu đến nó. Trong các thử nghiệm của tôi, điều này giúp nhiều hơn một chút so với việc kiểm tra thuộc tính GlobalAssuggingCache.

Tôi kết hợp cả hai thứ này và nhận được nó thậm chí còn nhanh hơn. Mã dưới đây bao gồm cả hai bộ lọc.

        string definedIn = typeof(XmlDecoderAttribute).Assembly.GetName().Name;
        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            // Note that we have to call GetName().Name.  Just GetName() will not work.  The following
            // if statement never ran when I tried to compare the results of GetName().
            if ((!assembly.GlobalAssemblyCache) && ((assembly.GetName().Name == definedIn) || assembly.GetReferencedAssemblies().Any(a => a.Name == definedIn)))
                foreach (Type type in assembly.GetTypes())
                    if (type.GetCustomAttributes(typeof(XmlDecoderAttribute), true).Length > 0)

4

Trong trường hợp giới hạn Portable .NET , đoạn mã sau sẽ hoạt động:

    public static IEnumerable<TypeInfo> GetAtributedTypes( Assembly[] assemblies, 
                                                           Type attributeType )
    {
        var typesAttributed =
            from assembly in assemblies
            from type in assembly.DefinedTypes
            where type.IsDefined(attributeType, false)
            select type;
        return typesAttributed;
    }

hoặc cho một số lượng lớn các hội đồng sử dụng dựa trên trạng thái vòng lặp yield return:

    public static IEnumerable<TypeInfo> GetAtributedTypes( Assembly[] assemblies, 
                                                           Type attributeType )
    {
        foreach (var assembly in assemblies)
        {
            foreach (var typeInfo in assembly.DefinedTypes)
            {
                if (typeInfo.IsDefined(attributeType, false))
                {
                    yield return typeInfo;
                }
            }
        }
    }

0

Chúng tôi có thể cải thiện câu trả lời của Andrew và chuyển đổi toàn bộ nội dung thành một truy vấn LINQ.

    public static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly)
    {
        return assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0);
    }
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.