Может ли класс C # наследовать атрибуты своего интерфейса?

114

Казалось бы, это означает «нет». Что прискорбно.

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

Может ли класс наследовать атрибуты интерфейса? Или я здесь не на то дерево лаю?

Роджер Липскомб
источник

Ответы:

73

Нет. При реализации интерфейса или переопределении членов в производном классе необходимо повторно объявить атрибуты.

Если вас интересует только 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 принимает аргумент, который сообщает, следует ли выполнять поиск в дереве наследования.
Руне Гримстад,
3
Хм. Я только что заметил, что речь идет о наследовании атрибутов от интерфейса, а не от базового класса.
Руне Гримстад,
В таком случае, есть ли смысл ставить атрибуты на интерфейсы?
Райан Пенфолд
5
@Ryan - конечно: для описания интерфейса. Например, сервисные контракты.
Marc Gravell
3
Марк (и @Rune): Да, ОП касалась интерфейсов. Но первое предложение вашего ответа может сбивать с толку: «... или переопределение членов в производном классе ...» - это не обязательно так. Вы можете заставить свой класс наследовать атрибуты своего базового класса. Вы не можете этого сделать только с интерфейсами. См. Также: stackoverflow.com/questions/12106566/…
chiccodoro
39

Вы можете определить полезный метод расширения ...

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))).
    Distinct().Cast<T>();
}
tanascius
источник
1
Он получает только атрибуты уровня типа, а не свойства, поля или члены, верно?
Маслоу
22
очень хорошо, я лично сейчас использую более короткую версию этого: private static IEnumerable <T> GetCustomAttributesIncludingBaseInterfaces <T> (этот тип Type) {var attributeType = typeof (T); возвращаемый тип.GetCustomAttributes (attributeType, true) .Union (type.GetInterfaces (). SelectMany (interfaceType => interfaceType.GetCustomAttributes (attributeType, true))). Distinct (). Cast <T> (); }
Саймон Д.
1
@SimonD .: И ваше реорганизованное решение работает быстрее.
mynkow
1
@SimonD, это стоило ответа, а не комментария.
Ник Н.
Есть ли причина не заменять Applyна встроенный ForEachотMicrosoft.Practices.ObjectBuilder2
Jacob Brewer
29

Статья Брэда Уилсона об этом: Атрибуты интерфейса! = Атрибуты класса

Подводя итог: классы не наследуются от интерфейсов, они их реализуют. Это означает, что атрибуты не являются частью реализации автоматически.

Если вам нужно наследовать атрибуты, используйте абстрактный базовый класс, а не интерфейс.

Роджер Липскомб
источник
Что делать, если у вас есть несколько реализуемых интерфейсов? Вы не можете просто преобразовать эти интерфейсы в абстрактные классы, потому что в C # отсутствует категория множественного наследования.
Энди
10

Хотя класс 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)
Питер Глюк
источник
4

Это больше для людей, желающих извлечь атрибуты из свойств, которые могут существовать в реализованном интерфейсе. Поскольку эти атрибуты не являются частью класса, это даст вам доступ к ним. обратите внимание, у меня есть простой контейнерный класс, который дает вам доступ к 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; }
    }
}
TravisWhidden
источник
0

РЕДАКТИРОВАТЬ: это касается наследования атрибутов от интерфейсов на членах (включая свойства). Выше есть простые ответы на определение типов. Я просто разместил это, потому что нашел это раздражающим ограничением и хотел поделиться решением :)

Интерфейсы являются множественным наследованием и ведут себя как наследование в системе типов. Для такого рода вещей нет веской причины. Отражение - это немного банально. Я добавил комментарии, чтобы объяснить чушь.

(Это .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

[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

Добавьте интерфейс со свойствами, которые имеют атрибуты / настраиваемые атрибуты, прикрепленные к тем же свойствам, что и класс. Мы можем извлечь интерфейс класса, используя функцию рефакторинга Visual Studio. Имейте частичный класс, реализующий этот интерфейс.

Теперь получите объект «Type» объекта класса и получите настраиваемые атрибуты из информации о свойстве, используя getProperties для объекта Type. Это не даст настраиваемые атрибуты для объекта класса, поскольку к свойствам класса не были прикреплены / унаследованы настраиваемые атрибуты свойств интерфейса.

Теперь вызовите GetInterface (NameOfImplemetedInterfaceByclass) для полученного выше объекта Type класса. Это предоставит интерфейс "Type" объект. мы должны знать ИМЯ реализованного интерфейса. Из объекта Type получить информацию о свойствах, и если к свойству интерфейса прикреплены какие-либо настраиваемые атрибуты, информация о свойстве предоставит список настраиваемых атрибутов. Реализующий класс должен обеспечить реализацию свойств интерфейса. Сопоставьте имя конкретного свойства объекта класса в списке информации о свойствах интерфейса, чтобы получить список настраиваемых атрибутов.

Это сработает.

user11432943
источник
0

Хотя мой ответ запоздалый и относится к определенному случаю, я хотел бы добавить несколько идей. Как предлагается в других ответах, Reflection или другие методы сделают это.

В моем случае свойство (временная метка) требовалось во всех моделях для удовлетворения определенного требования (атрибут проверки параллелизма) в основном проекте Entity framework. Мы могли либо добавить [] над всеми свойствами класса (добавление в интерфейс IModel, какие модели реализованы, не сработало). Но я сэкономил время с помощью Fluent API, который может пригодиться в этих случаях. В свободном API я могу проверить имя конкретного свойства во всех моделях и установить IsConcurrencyToken () в 1 строке !!

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

Аналогичным образом, если вам нужно добавить какой-либо атрибут к одному и тому же имени свойства в сотнях классов / моделей, мы можем использовать свободные методы api для встроенного или настраиваемого преобразователя атрибутов. Хотя EF (как ядро, так и EF6) fluent api может использовать отражение за кулисами, мы можем сэкономить усилия :)

Прасанна Венкатанатан
источник