C# 类可以从其接口继承属性吗?

Posted

技术标签:

【中文标题】C# 类可以从其接口继承属性吗?【英文标题】:Can a C# class inherit attributes from its interface? 【发布时间】:2010-10-07 03:30:28 【问题描述】:

这似乎意味着“不”。这是不幸的。

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

类可以从接口继承属性吗?还是我在这里找错树了?

【问题讨论】:

【参考方案1】:

没有。每当在派生类中实现接口或覆盖成员时,都需要重新声明属性。

如果您只关心 ComponentModel(不是直接反射),有一种方法 ([AttributeProvider]) 可以建议现有类型的属性(以避免重复),但它仅对属性和索引器的使用有效。

举个例子:

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

输出:

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

【讨论】:

你确定吗? MemberInfo.GetCustomAttributes 方法接受一个参数,指示是否应搜索继承树。 嗯。我刚刚注意到问题是关于从接口而不是基类继承属性。 那么有什么理由在接口上放置属性吗? @Ryan - 确定:用于描述界面。例如,服务合同。 Marc(和@Rune):是的,OP 是关于接口的。但是您的答案的第一句话可能会令人困惑:“......或在派生类中覆盖成员......” - 这不一定是真的。您可以让您的类从其基类继承属性。你不能用接口来做到这一点。另见:***.com/questions/12106566/…【参考方案2】:

你可以定义一个有用的扩展方法...

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

扩展方法如下:

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

更新:

这是 SimonD 在评论中提出的较短版本:

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)))
    .Cast<T>();

【讨论】:

这只会获取类型级别的属性,而不是属性、字段或成员,对吧? 非常好,我个人使用这个的更短版本,现在: private static IEnumerable GetCustomAttributesIncludingBaseInterfaces(this Type type) var attributeType = typeof(T); return type.GetCustomAttributes(attributeType, true).Union(type.GetInterfaces().SelectMany(interfaceType => interfaceType.GetCustomAttributes(attributeType, true))).Distinct().Cast(); @SimonD.:你重构的解决方案更快。 @SimonD 这值得回答,而不是评论。 有什么理由不用Microsoft.Practices.ObjectBuilder2的内置ForEach替换Apply【参考方案3】:

Brad Wilson 的一篇关于此的文章:Interface Attributes != Class Attributes

总结一下:类不继承接口,而是实现接口。这意味着属性不会自动成为实现的一部分。

如果需要继承属性,请使用抽象基类,而不是接口。

【讨论】:

如果您要实现多个接口怎么办?您不能只将这些接口更改为抽象类,因为 C# 缺少多重继承类别。【参考方案4】:

虽然 C# 类不从其接口继承属性,但在 ASP.NET MVC3 中绑定模型时有一个有用的替代方法。

如果您将视图的模型声明为接口而不是具体类型,则视图和模型绑定器将在渲染和验证模型时应用接口中的属性(例如,[Required][DisplayName("Foo")]

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

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

然后在视图中:

@* 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)

【讨论】:

【参考方案5】:

这更适合希望从已实现接口上可能存在的属性中提取属性的人。因为这些属性不是类的一部分,所以这将使您可以访问它们。请注意,我有一个简单的容器类,可让您访问 PropertyInfo - 因为这正是我需要的。根据需要进行破解。这对我很有效。

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; 
    

【讨论】:

【参考方案6】:

编辑:这包括从成员的接口继承属性(包括属性)。上面有类型定义的简单答案。我刚刚发布了这个,因为我发现它是一个令人讨厌的限制并想分享一个解决方案:)

接口是多重继承,在类型系统中表现为继承。这种事情没有充分的理由。反射有点胡作非为。我已经添加了 cmets 来解释这些废话。

(这是 .NET 3.5,因为它恰好是我目前正在使用的项目。)

// 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;
    

准系统 NUnit 测试

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

【讨论】:

【参考方案7】:

添加具有属性/自定义属性的接口,这些属性附加到类具有的相同属性。我们可以使用 Visual Studio 重构功能提取类的接口。 有一个部分类实现该接口。

现在获取类对象的“类型”对象,并使用类型对象上的 getProperties 从属性信息中获取自定义属性。 这不会在类对象上提供自定义属性,因为类属性没有附加/继承接口属性的自定义属性。

现在对上面检索到的类的 Type 对象调用 GetInterface(NameOfImplementedInterfaceByclass)。这会 提供接口的“类型”对象。我们应该知道实现的接口的名称。从 Type 对象获取属性信息,如果接口的属性附加了任何自定义属性,则属性信息将提供 自定义属性列表。实现类必须提供接口属性的实现。 在接口的属性信息列表中匹配类对象的具体属性名称,得到自定义属性列表。

这会起作用。

【讨论】:

【参考方案8】:

虽然我的回答较晚且针对特定案例,但我想补充一些想法。 正如其他答案中所建议的那样,反射或其他方法可以做到。

在我的例子中,所有模型都需要一个属性(时间戳)来满足实体框架核心项目中的某些要求(并发检查属性)。 我们可以在所有类属性之上添加 [] (在模型实现的 IModel 接口中添加,不起作用)。但是我通过 Fluent API 节省了时间,这在这些情况下很有帮助。在 fluent API 中,我可以检查所有模型中的特定属性名称,并在 1 行中设置为 IsConcurrencyToken() !!

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

同样,如果您需要将任何属性添加到 100 个类/模型中的相同属性名称,我们可以使用流利的 api 方法进行内置或自定义属性解析器。 虽然 EF(core 和 EF6)fluent api 可能在幕后使用反射,但我们可以省力:)

【讨论】:

以上是关于C# 类可以从其接口继承属性吗?的主要内容,如果未能解决你的问题,请参考以下文章

C# 浅谈 接口(Interface)的作用

Unity3D 接口使用

C#接口

C# 基础知识

c#中public和protect可以被子类继承下去吗

Java的类与接口有什么作用?支持多继承吗?