Lớp C # có thể kế thừa các thuộc tính từ giao diện của nó không?


114

Điều này dường như ngụ ý "không". Thật là đáng tiếc.

[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class,
 AllowMultiple = true, Inherited = true)]
public class CustomDescriptionAttribute : Attribute
{
    public string Description { get; private set; }

    public CustomDescriptionAttribute(string description)
    {
        Description = description;
    }
}

[CustomDescription("IProjectController")]
public interface IProjectController
{
    void Create(string projectName);
}

internal class ProjectController : IProjectController
{
    public void Create(string projectName)
    {
    }
}

[TestFixture]
public class CustomDescriptionAttributeTests
{
    [Test]
    public void ProjectController_ShouldHaveCustomDescriptionAttribute()
    {
        Type type = typeof(ProjectController);
        object[] attributes = type.GetCustomAttributes(
            typeof(CustomDescriptionAttribute),
            true);

        // NUnit.Framework.AssertionException:   Expected: 1   But was:  0
        Assert.AreEqual(1, attributes.Length);
    }
}

Một lớp có thể kế thừa các thuộc tính từ một giao diện không? Hay là tôi sủa nhầm cây đây?

Câu trả lời:


73

Không. Bất cứ khi nào triển khai một giao diện hoặc ghi đè các thành viên trong một lớp dẫn xuất, bạn cần phải khai báo lại các thuộc tính.

Nếu bạn chỉ quan tâm đến ComponentModel (không phản ánh trực tiếp), có một cách ( [AttributeProvider]) đề xuất các thuộc tính từ một kiểu hiện có (để tránh trùng lặp), nhưng nó chỉ hợp lệ cho việc sử dụng thuộc tính và trình chỉ mục.

Ví dụ:

using System;
using System.ComponentModel;
class Foo {
    [AttributeProvider(typeof(IListSource))]
    public object Bar { get; set; }

    static void Main() {
        var bar = TypeDescriptor.GetProperties(typeof(Foo))["Bar"];
        foreach (Attribute attrib in bar.Attributes) {
            Console.WriteLine(attrib);
        }
    }
}

kết quả đầu ra:

System.SerializableAttribute
System.ComponentModel.AttributeProviderAttribute
System.ComponentModel.EditorAttribute
System.Runtime.InteropServices.ComVisibleAttribute
System.Runtime.InteropServices.ClassInterfaceAttribute
System.ComponentModel.TypeConverterAttribute
System.ComponentModel.MergablePropertyAttribute

Bạn có chắc về điều này? Phương thức MemberInfo.GetCustomAttributes nhận một đối số cho biết cây kế thừa có nên được tìm kiếm hay không.
Rune Grimstad

3
Hừ! Tôi chỉ nhận thấy rằng câu hỏi là về việc kế thừa các thuộc tính từ một giao diện không phải từ một lớp cơ sở.
Rune Grimstad

Có lý do gì để đặt các thuộc tính trên giao diện không?
Ryan Penfold

5
@Ryan - chắc chắn: để mô tả giao diện. Ví dụ, hợp đồng dịch vụ.
Marc Gravell

3
Marc (và @Rune): Vâng, OP là về giao diện. Nhưng câu đầu tiên trong câu trả lời của bạn có thể gây nhầm lẫn: "... hoặc ghi đè các thành viên trong một lớp dẫn xuất ..." - điều này không nhất thiết đúng. Bạn có thể có các thuộc tính kế thừa lớp của mình từ lớp cơ sở của nó. Bạn chỉ không thể làm điều đó với các giao diện. Xem thêm: stackoverflow.com/questions/12106566/...
chiccodoro

39

Bạn có thể xác định một phương pháp mở rộng hữu ích ...

Type type = typeof(ProjectController);
var attributes = type.GetCustomAttributes<CustomDescriptionAttribute>( true );

Đây là phương thức mở rộng:

/// <summary>Searches and returns attributes. The inheritance chain is not used to find the attributes.</summary>
/// <typeparam name="T">The type of attribute to search for.</typeparam>
/// <param name="type">The type which is searched for the attributes.</param>
/// <returns>Returns all attributes.</returns>
public static T[] GetCustomAttributes<T>( this Type type ) where T : Attribute
{
  return GetCustomAttributes( type, typeof( T ), false ).Select( arg => (T)arg ).ToArray();
}

/// <summary>Searches and returns attributes.</summary>
/// <typeparam name="T">The type of attribute to search for.</typeparam>
/// <param name="type">The type which is searched for the attributes.</param>
/// <param name="inherit">Specifies whether to search this member's inheritance chain to find the attributes. Interfaces will be searched, too.</param>
/// <returns>Returns all attributes.</returns>
public static T[] GetCustomAttributes<T>( this Type type, bool inherit ) where T : Attribute
{
  return GetCustomAttributes( type, typeof( T ), inherit ).Select( arg => (T)arg ).ToArray();
}

/// <summary>Private helper for searching attributes.</summary>
/// <param name="type">The type which is searched for the attribute.</param>
/// <param name="attributeType">The type of attribute to search for.</param>
/// <param name="inherit">Specifies whether to search this member's inheritance chain to find the attribute. Interfaces will be searched, too.</param>
/// <returns>An array that contains all the custom attributes, or an array with zero elements if no attributes are defined.</returns>
private static object[] GetCustomAttributes( Type type, Type attributeType, bool inherit )
{
  if( !inherit )
  {
    return type.GetCustomAttributes( attributeType, false );
  }

  var attributeCollection = new Collection<object>();
  var baseType = type;

  do
  {
    baseType.GetCustomAttributes( attributeType, true ).Apply( attributeCollection.Add );
    baseType = baseType.BaseType;
  }
  while( baseType != null );

  foreach( var interfaceType in type.GetInterfaces() )
  {
    GetCustomAttributes( interfaceType, attributeType, true ).Apply( attributeCollection.Add );
  }

  var attributeArray = new object[attributeCollection.Count];
  attributeCollection.CopyTo( attributeArray, 0 );
  return attributeArray;
}

/// <summary>Applies a function to every element of the list.</summary>
private static void Apply<T>( this IEnumerable<T> enumerable, Action<T> function )
{
  foreach( var item in enumerable )
  {
    function.Invoke( item );
  }
}

Cập nhật:

Đây là một phiên bản ngắn hơn do SimonD đề xuất trong một bình luận:

private static IEnumerable<T> GetCustomAttributesIncludingBaseInterfaces<T>(this Type type)
{
  var attributeType = typeof(T);
  return type.GetCustomAttributes(attributeType, true).
    Union(type.GetInterfaces().
    SelectMany(interfaceType => interfaceType.GetCustomAttributes(attributeType, true))).
    Distinct().Cast<T>();
}

1
Điều này chỉ nhận thuộc tính cấp loại, không phải thuộc tính, trường hoặc thành viên, phải không?
Maslow

22
rất hay, cá nhân tôi hiện đang sử dụng một phiên bản ngắn hơn của cái này: private static IEnumerable <T> GetCustomAttributesIncludingBaseInterfaces <T> (loại Type này) {var thuộc tínhType = typeof (T); return type.GetCustomAttributes (thuộc tínhType, true) .Union (type.GetInterfaces (). SelectMany (interfaceType => interfaceType.GetCustomAttributes (thuộc tínhType, true))). Distinction (). Cast <T> (); }
Simon D.

1
@SimonD.: Và giải pháp được tái cấu trúc của bạn nhanh hơn.
mynkow

1
@SimonD đây là một câu trả lời đáng giá, thay vì một bình luận.
Nick N.

Có lý do nào đó không để thay thế Applyvới việc xây dựng trong ForEachtừMicrosoft.Practices.ObjectBuilder2
Jacob Brewer

29

Một bài viết của Brad Wilson về điều này: Giao diện thuộc tính! = Thuộc tính lớp

Tóm lại: các lớp không kế thừa từ các giao diện, chúng thực thi chúng. Điều này có nghĩa là các thuộc tính không tự động là một phần của việc triển khai.

Nếu bạn cần kế thừa các thuộc tính, hãy sử dụng một lớp cơ sở trừu tượng, thay vì một giao diện.


Điều gì xảy ra nếu bạn có nhiều giao diện đang triển khai? Bạn không thể chỉ thay đổi các giao diện đó thành các lớp trừu tượng vì C # thiếu thể loại đa kế thừa.
Andy

10

Trong khi lớp C # không kế thừa các thuộc tính từ các giao diện của nó, có một giải pháp thay thế hữu ích khi ràng buộc các mô hình trong ASP.NET MVC3.

Nếu bạn khai báo mô hình của chế độ xem là giao diện chứ không phải là kiểu cụ thể, thì chế độ xem và chất kết dính mô hình sẽ áp dụng các thuộc tính (ví dụ: [Required]hoặc [DisplayName("Foo")]từ giao diện khi hiển thị và xác thực mô hình:

public interface IModel {
    [Required]
    [DisplayName("Foo Bar")]
    string FooBar { get; set; }
} 

public class Model : IModel {
    public string FooBar { get; set; }
}

Sau đó, trong chế độ xem:

@* Note use of interface type for the view model *@
@model IModel 

@* This control will receive the attributes from the interface *@
@Html.EditorFor(m => m.FooBar)

4

Điều này nhiều hơn cho những người muốn trích xuất các thuộc tính từ các thuộc tính có thể tồn tại trên một giao diện được triển khai. Vì những thuộc tính đó không phải là một phần của lớp, điều này sẽ cung cấp cho bạn quyền truy cập vào chúng. lưu ý, tôi có một lớp vùng chứa đơn giản cho phép bạn truy cập vào PropertyInfo - vì đó là những gì tôi cần nó. Hack lên khi bạn cần. Điều này làm việc tốt cho tôi.

public static class CustomAttributeExtractorExtensions
{
    /// <summary>
    /// Extraction of property attributes as well as attributes on implemented interfaces.
    /// This will walk up recursive to collect any interface attribute as well as their parent interfaces.
    /// </summary>
    /// <typeparam name="TAttributeType"></typeparam>
    /// <param name="typeToReflect"></param>
    /// <returns></returns>
    public static List<PropertyAttributeContainer<TAttributeType>> GetPropertyAttributesFromType<TAttributeType>(this Type typeToReflect)
        where TAttributeType : Attribute
    {
        var list = new List<PropertyAttributeContainer<TAttributeType>>();

        // Loop over the direct property members
        var properties = typeToReflect.GetProperties();

        foreach (var propertyInfo in properties)
        {
            // Get the attributes as well as from the inherited classes (true)
            var attributes = propertyInfo.GetCustomAttributes<TAttributeType>(true).ToList();
            if (!attributes.Any()) continue;

            list.AddRange(attributes.Select(attr => new PropertyAttributeContainer<TAttributeType>(attr, propertyInfo)));
        }

        // Look at the type interface declarations and extract from that type.
        var interfaces = typeToReflect.GetInterfaces();

        foreach (var @interface in interfaces)
        {
            list.AddRange(@interface.GetPropertyAttributesFromType<TAttributeType>());
        }

        return list;

    }

    /// <summary>
    /// Simple container for the Property and Attribute used. Handy if you want refrence to the original property.
    /// </summary>
    /// <typeparam name="TAttributeType"></typeparam>
    public class PropertyAttributeContainer<TAttributeType>
    {
        internal PropertyAttributeContainer(TAttributeType attribute, PropertyInfo property)
        {
            Property = property;
            Attribute = attribute;
        }

        public PropertyInfo Property { get; private set; }

        public TAttributeType Attribute { get; private set; }
    }
}

0

EDIT: điều này bao gồm việc kế thừa các thuộc tính từ các giao diện trên các thành viên (bao gồm các thuộc tính). Có những câu trả lời đơn giản ở trên cho các định nghĩa loại. Tôi chỉ đăng bài này vì tôi thấy nó là một hạn chế khó chịu và muốn chia sẻ một giải pháp :)

Các giao diện là đa kế thừa và hoạt động như một kế thừa trong hệ thống kiểu. Không có lý do chính đáng cho loại công cụ này. Suy ngẫm là một chút hokey. Tôi đã thêm nhận xét để giải thích những điều vô nghĩa.

(Đây là .NET 3.5 vì đây là dự án mà tôi đang làm vào lúc này đang sử dụng.)

// in later .NETs, you can cache reflection extensions using a static generic class and
// a ConcurrentDictionary. E.g.
//public static class Attributes<T> where T : Attribute
//{
//    private static readonly ConcurrentDictionary<MemberInfo, IReadOnlyCollection<T>> _cache =
//        new ConcurrentDictionary<MemberInfo, IReadOnlyCollection<T>>();
//
//    public static IReadOnlyCollection<T> Get(MemberInfo member)
//    {
//        return _cache.GetOrAdd(member, GetImpl, Enumerable.Empty<T>().ToArray());
//    }
//    //GetImpl as per code below except that recursive steps re-enter via the cache
//}

public static List<T> GetAttributes<T>(this MemberInfo member) where T : Attribute
{
    // determine whether to inherit based on the AttributeUsage
    // you could add a bool parameter if you like but I think it defeats the purpose of the usage
    var usage = typeof(T).GetCustomAttributes(typeof(AttributeUsageAttribute), true)
        .Cast<AttributeUsageAttribute>()
        .FirstOrDefault();
    var inherit = usage != null && usage.Inherited;

    return (
        inherit
            ? GetAttributesRecurse<T>(member)
            : member.GetCustomAttributes(typeof (T), false).Cast<T>()
        )
        .Distinct()  // interfaces mean duplicates are a thing
        // note: attribute equivalence needs to be overridden. The default is not great.
        .ToList();
}

private static IEnumerable<T> GetAttributesRecurse<T>(MemberInfo member) where T : Attribute
{
    // must use Attribute.GetCustomAttribute rather than MemberInfo.GetCustomAttribute as the latter
    // won't retrieve inherited attributes from base *classes*
    foreach (T attribute in Attribute.GetCustomAttributes(member, typeof (T), true))
        yield return attribute;

    // The most reliable target in the interface map is the property get method.
    // If you have set-only properties, you'll need to handle that case. I generally just ignore that
    // case because it doesn't make sense to me.
    PropertyInfo property;
    var target = (property = member as PropertyInfo) != null ? property.GetGetMethod() : member;

    foreach (var @interface in member.DeclaringType.GetInterfaces())
    {
        // The interface map is two aligned arrays; TargetMethods and InterfaceMethods.
        var map = member.DeclaringType.GetInterfaceMap(@interface);
        var memberIndex = Array.IndexOf(map.TargetMethods, target); // see target above
        if (memberIndex < 0) continue;

        // To recurse, we still need to hit the property on the parent interface.
        // Why don't we just use the get method from the start? Because GetCustomAttributes won't work.
        var interfaceMethod = property != null
            // name of property get method is get_<property name>
            // so name of parent property is substring(4) of that - this is reliable IME
            ? @interface.GetProperty(map.InterfaceMethods[memberIndex].Name.Substring(4))
            : (MemberInfo) map.InterfaceMethods[memberIndex];

        // Continuation is the word to google if you don't understand this
        foreach (var attribute in interfaceMethod.GetAttributes<T>())
            yield return attribute;
    }
}

Thử nghiệm NUnit xương trần

[TestFixture]
public class GetAttributesTest
{
    [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
    private sealed class A : Attribute
    {
        // default equality for Attributes is apparently semantic
        public override bool Equals(object obj)
        {
            return ReferenceEquals(this, obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }

    [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
    private sealed class ANotInherited : Attribute { }

    public interface Top
    {
        [A, ANotInherited]
        void M();

        [A, ANotInherited]
        int P { get; }
    }

    public interface Middle : Top { }

    private abstract class Base
    {
        [A, ANotInherited]
        public abstract void M();

        [A, ANotInherited]
        public abstract int P { get; }
    }

    private class Bottom : Base, Middle
    {
        [A, ANotInherited]
        public override void M()
        {
            throw new NotImplementedException();
        }

        [A, ANotInherited]
        public override int P { get { return 42; } }
    }

    [Test]
    public void GetsAllInheritedAttributesOnMethods()
    {
        var attributes = typeof (Bottom).GetMethod("M").GetAttributes<A>();
        attributes.Should()
            .HaveCount(3, "there are 3 inherited copies in the class heirarchy and A is inherited");
    }

    [Test]
    public void DoesntGetNonInheritedAttributesOnMethods()
    {
        var attributes = typeof (Bottom).GetMethod("M").GetAttributes<ANotInherited>();
        attributes.Should()
            .HaveCount(1, "it shouldn't get copies of the attribute from base classes for a non-inherited attribute");
    }

    [Test]
    public void GetsAllInheritedAttributesOnProperties()
    {
        var attributes = typeof(Bottom).GetProperty("P").GetAttributes<A>();
        attributes.Should()
            .HaveCount(3, "there are 3 inherited copies in the class heirarchy and A is inherited");
    }

    [Test]
    public void DoesntGetNonInheritedAttributesOnProperties()
    {
        var attributes = typeof(Bottom).GetProperty("P").GetAttributes<ANotInherited>();
        attributes.Should()
            .HaveCount(1, "it shouldn't get copies of the attribute from base classes for a non-inherited attribute");
    }
}

0

Thêm giao diện với các thuộc tính có thuộc tính / thuộc tính tùy chỉnh được gắn vào cùng thuộc tính mà lớp có. Chúng ta có thể trích xuất giao diện của lớp bằng cách sử dụng tính năng tái cấu trúc Visual studio. Yêu cầu một phần lớp triển khai giao diện đó.

Bây giờ Lấy đối tượng "Loại" của đối tượng lớp và nhận các thuộc tính tùy chỉnh từ thông tin thuộc tính bằng cách sử dụng getProperties trên đối tượng Loại. Điều này sẽ không cung cấp các thuộc tính tùy chỉnh trên đối tượng lớp vì các thuộc tính lớp không có các thuộc tính tùy chỉnh của thuộc tính giao diện được đính kèm / kế thừa.

Bây giờ hãy gọi GetInterface (NameOfImplemetedInterfaceByclass) trên đối tượng Type của lớp được truy xuất ở trên. Điều này sẽ cung cấp đối tượng "Loại" của giao diện. chúng ta nên biết NAME của giao diện được triển khai. Từ đối tượng Type lấy thông tin thuộc tính và nếu thuộc tính của giao diện có bất kỳ thuộc tính tùy chỉnh nào được đính kèm thì thông tin thuộc tính sẽ cung cấp danh sách thuộc tính tùy chỉnh. Lớp thực thi phải cung cấp việc triển khai các thuộc tính của giao diện. Khớp tên thuộc tính cụ thể của đối tượng lớp trong danh sách thông tin thuộc tính của giao diện để có được danh sách thuộc tính tùy chỉnh.

Điều này sẽ hoạt động.


0

Mặc dù câu trả lời của tôi là muộn và cụ thể cho một trường hợp nhất định, tôi muốn bổ sung một số ý kiến. Như đã đề xuất trong các câu trả lời khác, Reflection hoặc các phương pháp khác sẽ làm được điều đó.

Trong trường hợp của tôi, một thuộc tính (dấu thời gian) là cần thiết trong tất cả các mô hình để đáp ứng yêu cầu nhất định (thuộc tính kiểm tra đồng thời) trong một dự án cốt lõi của Entity framework. Chúng ta có thể thêm [] lên trên tất cả các thuộc tính của lớp (thêm vào giao diện IModel mà các mô hình đã triển khai, không hoạt động). Nhưng tôi đã tiết kiệm thời gian thông qua API Fluent rất hữu ích trong những trường hợp này. Với API thông thạo, tôi có thể kiểm tra tên thuộc tính cụ thể trong tất cả các mô hình và đặt là IsConcurrencyToken () trong 1 dòng !!

var props = from e in modelBuilder.Model.GetEntityTypes()
            from p in e.GetProperties()
            select p;
props.Where(p => p.PropertyInfo.Name == "ModifiedTime").ToList().ForEach(p => { p.IsConcurrencyToken = true; });

Tương tự như vậy nếu bạn cần thêm bất kỳ thuộc tính nào vào cùng tên thuộc tính trong 100 lớp / mô hình, chúng tôi có thể sử dụng các phương thức api thông thạo cho trình phân giải thuộc tính có sẵn hoặc tùy chỉnh. Mặc dù api thông thạo EF (cả lõi và EF6) có thể sử dụng sự phản chiếu đằng sau hậu trường, chúng tôi có thể tiết kiệm công sức :)

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.