Uma classe C # pode herdar atributos de sua interface?


114

Isso pareceria implicar "não". O que é lamentável.

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

Uma classe pode herdar atributos de uma interface? Ou estou latindo para a árvore errada aqui?

Respostas:


73

Não. Sempre que implementar uma interface ou substituir membros em uma classe derivada, você precisará declarar novamente os atributos.

Se você se preocupa apenas com ComponentModel (não com reflexão direta), existe uma maneira ([AttributeProvider] ) de sugerir atributos de um tipo existente (para evitar a duplicação), mas isso só é válido para uso de propriedade e indexador.

Como um exemplo:

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

saídas:

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

Você tem certeza disso? O método MemberInfo.GetCustomAttributes recebe um argumento que informa se a árvore de herança deve ser pesquisada.
Rune Grimstad

3
Hmm. Acabei de notar que a questão é sobre herdar atributos de uma interface, não de uma classe base.
Rune Grimstad

Existe alguma razão para colocar atributos nas interfaces então?
Ryan Penfold

5
@Ryan - claro: para descrever a interface. Por exemplo, contratos de serviço.
Marc Gravell

3
Marc (e @Rune): Sim, o OP era sobre interfaces. Mas a primeira frase de sua resposta pode ser confusa: "... ou substituindo membros em uma classe derivada ..." - isso não é necessariamente verdade. Você pode fazer com que sua classe herde atributos de sua classe base. Você só não pode fazer isso com interfaces. Veja também: stackoverflow.com/questions/12106566/…
chiccodoro

39

Você pode definir um método de extensão útil ...

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

Aqui está o método de extensão:

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

Atualizar:

Aqui está uma versão mais curta proposta por SimonD em um comentário:

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
Isso só obtém atributos de nível de tipo, não propriedades, campos ou membros, certo?
Maslow

22
muito bom, eu pessoalmente uso uma versão mais curta disso, agora: private static IEnumerable <T> GetCustomAttributesIncludingBaseInterfaces <T> (este tipo de tipo) {var attributeType = typeof (T); return type.GetCustomAttributes (attributeType, true) .Union (type.GetInterfaces (). SelectMany (interfaceType => interfaceType.GetCustomAttributes (attributeType, true))). Distinct (). Cast <T> (); }
Simon D.

1
@SimonD .: E sua solução refatorada é mais rápida.
mynkow

1
@SimonD valeu uma resposta, ao invés de um comentário.
Nick N.

Há alguma razão para não substituir Applypelo integrado ForEachdeMicrosoft.Practices.ObjectBuilder2
Jacob Brewer

29

Um artigo de Brad Wilson sobre isso: Atributos de interface! = Atributos de classe

Para resumir: as classes não herdam de interfaces, elas as implementam. Isso significa que os atributos não fazem parte automaticamente da implementação.

Se você precisar herdar atributos, use uma classe base abstrata, em vez de uma interface.


E se você tiver várias interfaces que está implementando? Você não pode simplesmente mudar essas interfaces em classes abstratas porque C # está ausente na categoria de herança múltipla.
Andy

10

Embora uma classe C # não herde atributos de suas interfaces, há uma alternativa útil ao vincular modelos na ASP.NET MVC3.

Se você declarar que o modelo da visualização é a interface em vez do tipo concreto, a visualização e o fichário do modelo aplicarão os atributos (por exemplo, [Required]ou [DisplayName("Foo")]da interface ao renderizar e validar o modelo:

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

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

Então, na visualização:

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

Isso é mais para pessoas que procuram extrair atributos de propriedades que podem existir em uma interface implementada. Como esses atributos não fazem parte da classe, você terá acesso a eles. observe, eu tenho uma classe de contêiner simples que fornece acesso a PropertyInfo - pois é para isso que eu preciso. Hacke quando você precisar. Isto funcionou bem para mim.

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

EDITAR: cobre a herança de atributos de interfaces em membros (incluindo propriedades). Existem respostas simples acima para definições de tipo. Acabei de postar isso porque achei uma limitação irritante e queria compartilhar uma solução :)

As interfaces são de herança múltipla e se comportam como herança no sistema de tipos. Não há um bom motivo para esse tipo de coisa. A reflexão é um pouco piegas. Eu adicionei comentários para explicar o absurdo.

(Este é o .NET 3.5 porque é simplesmente o que o projeto que estou fazendo no momento está usando.)

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

Teste 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

Adicione interface com propriedades que possuem atributos / atributos personalizados anexados às mesmas propriedades que a classe possui. Podemos extrair a interface da classe usando o recurso de refatoração do Visual Studio. Faça com que uma classe parcial implemente essa interface.

Agora obtenha o objeto "Type" do objeto de classe e obtenha atributos personalizados das informações da propriedade usando getProperties no objeto Type. Isso não fornecerá os atributos personalizados no objeto de classe, pois as propriedades da classe não tinham os atributos personalizados das propriedades da interface anexados / herdados.

Agora chame GetInterface (NameOfImplemetedInterfaceByclass) no objeto Type da classe recuperado acima. Isso fornecerá o objeto "Tipo" da interface. devemos saber o NOME da interface implementada. Do objeto Type, obtenha informações de propriedade e, se a propriedade da interface tiver algum atributo personalizado anexado, as informações de propriedade fornecerão uma lista de atributos personalizados. A classe de implementação deve ter fornecido a implementação das propriedades da interface. Combine o nome da propriedade específica do objeto de classe na lista de informações de propriedade da interface para obter a lista de atributos personalizados.

Isso vai funcionar.


0

Embora minha resposta seja tardia e específica para um determinado caso, gostaria de acrescentar algumas idéias. Como sugerido em outras respostas, Reflexão ou outros métodos bastariam.

No meu caso, uma propriedade (carimbo de data / hora) era necessária em todos os modelos para atender a certos requisitos (atributo de verificação de simultaneidade) em um projeto principal do framework Entity. Poderíamos adicionar [] acima de todas as propriedades de classe (adicionar na interface IModel que os modelos implementaram, não funcionou). Mas economizei tempo com a API Fluent, que é útil nesses casos. Na API fluente, posso verificar o nome da propriedade específica em todos os modelos e definir como IsConcurrencyToken () em 1 linha !!

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

Da mesma forma, se você precisar que qualquer atributo seja adicionado ao mesmo nome de propriedade em centenas de classes / modelos, podemos usar métodos de API fluentes para resolvedor de atributo embutido ou personalizado. Embora a API fluente EF (core e EF6) possa usar reflexão nos bastidores, podemos economizar esforços :)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.