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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/540749/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 07:23:54  来源:igfitidea点击:

Can a C# class inherit attributes from its interface?

c#attributes

提问by Roger Lipscombe

This would appear to imply "no". Which is unfortunate.

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

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

Can a class inherit attributes from an interface? Or am I barking up the wrong tree here?

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

采纳答案by Marc Gravell

No. Whenever implementing an interface or overriding members in a derived class, you need to re-declare the attributes.

不可以。无论何时在派生类中实现接口或覆盖成员,都需要重新声明属性。

If you only care about ComponentModel (not direct reflection), there is a way ([AttributeProvider]) of suggesting attributes from an existing type (to avoid duplication), but it is only valid for property and indexer usage.

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

As an example:

举个例子:

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

outputs:

输出:

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

回答by tanascius

You can define a useful extension method ...

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

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

Here is the extension method:

下面是扩展方法:

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

Update:

更新:

Here is a shorter version as proposed by SimonD in a comment:

这是 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))).
    Distinct().Cast<T>();
}

回答by Roger Lipscombe

An article by Brad Wilson about this: Interface Attributes != Class Attributes

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

To summarise: classes don't inherit from interfaces, they implement them. This means that the attributes are not automatically part of the implementation.

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

If you need to inherit attributes, use an abstract base class, rather than an interface.

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

回答by Peter Gluck

While a C# class does not inherit attributes from its interfaces, there is a useful alternative when binding models in ASP.NET MVC3.

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

If you declare the view's model to be the interface rather than the concrete type, then the view and the model binder will apply the attributes (e.g., [Required]or [DisplayName("Foo")]from the interface when rendering and validating the model:

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

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

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

Then in the view:

然后在视图中:

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

回答by TravisWhidden

This is more for people looking to extract attributes from properties that may exist on an implemented interface. Because those attributes are not part of the class, this will give you access to them. note, I have a simple container class that gives you access to the PropertyInfo - as that is what I needed it for. Hack up as you need. This worked well for me.

这更适合希望从已实现接口上可能存在的属性中提取属性的人。因为这些属性不是类的一部分,所以您可以访问它们。请注意,我有一个简单的容器类,可让您访问 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; }
    }
}

回答by Seth

EDIT: this covers inheriting attributes from interfaces on members (incl. properties). There are simple answers above for type definitions. I just posted this because I found it to be an irritating limitation and wanted to share a solution :)

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

Interfaces are multiple inheritance and behave as inheritance in the type system. There isn't a good reason for this kind of stuff. Reflection is a bit hokey. I've added comments to explain the nonsense.

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

(This is .NET 3.5 because this it just happens to be what the project I'm doing at the moment is using.)

(这是 .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;
    }
}

Barebones NUnit test

准系统 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");
    }
}

回答by user11432943

Add interface with properties that have attributes/custom attributes attached to the same properties that class have. We can extract the interface of the class by using Visual studio refactor feature. Have a partial class implement that interface.

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

Now Get "Type" object of the class object and get custom attributes from the property info using getProperties on Type object. This will not give the custom attributes on the class object as the class properties did not had the interface properties' custom attributes attached/inherited.

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

Now call GetInterface(NameOfImplemetedInterfaceByclass) on the class's Type object retrieved above. This will provide the interface's "Type" object. we should know the implemented interface's NAME. From Type object get property information and if the interface's property has any custom attributes attached then property information will provide custom attribute list. The implementing class must have provided implementation of the interface's properties. Match the class object's specific property name within the list of the interface's property information to get the custom attributes list.

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

This will work.

这将起作用。

回答by Prasanna Venkatanathan

Though my answer is late and specific to a certain case, I would like to add some ideas. As suggested in other answers, Reflection or other methods would do it.

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

In my case a property (timestamp) was needed in all models to meet certain requirement (concurrency check attribute) in a Entity framework core project. We could either add [] above all class properties (adding in IModel interface which models implemented, didn't work). But I saved time through Fluent API which is helpful in these cases. In fluent API, I can check for specific property name in all models and set as IsConcurrencyToken() in 1 line !!

在我的例子中,所有模型都需要一个属性(时间戳)来满足实体框架核心项目中的某些要求(并发检查属性)。我们可以在所有类属性上方添加 [](在 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; });

Likewise if you need any attribute to be added to same property name in 100's of classes/models, we can use fluent api methods for inbuilt or custom attribute resolver. Though EF (both core and EF6) fluent api may use reflection behind the scenes, we can save effort :)

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