¿Puede una clase C # heredar atributos de su interfaz?


114

Esto parecería implicar "no". Lo cual es lamentable.

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

¿Puede una clase heredar atributos de una interfaz? ¿O estoy ladrando al árbol equivocado aquí?

Respuestas:


73

No. Siempre que implemente una interfaz o anule miembros en una clase derivada, debe volver a declarar los atributos.

Si solo le importa ComponentModel (no la reflexión directa), hay una manera ([AttributeProvider] ) de sugerir atributos de un tipo existente (para evitar la duplicación), pero solo es válido para el uso de propiedades e indexadores.

Como ejemplo:

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

salidas:

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

¿Estas seguro acerca de esto? El método MemberInfo.GetCustomAttributes toma un argumento que indica si se debe buscar en el árbol de herencia.
Rune Grimstad

3
Hmm. Acabo de notar que la pregunta se trata de heredar atributos de una interfaz, no de una clase base.
Rune Grimstad

Entonces, ¿hay alguna razón para poner atributos en las interfaces?
Ryan Penfold

5
@Ryan - seguro: por describir la interfaz. Por ejemplo, contratos de servicios.
Marc Gravell

3
Marc (y @Rune): Sí, el OP se trataba de interfaces. Pero la primera oración de su respuesta puede ser confusa: "... o miembros anulados en una clase derivada ..." - esto no es necesariamente cierto. Puede hacer que su clase herede atributos de su clase base. Solo no puedes hacer eso con interfaces. Ver también: stackoverflow.com/questions/12106566/…
chiccodoro

39

Puede definir un método de extensión útil ...

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

Aquí está el método de extensión:

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

Actualizar:

Aquí hay una versión más corta propuesta por SimonD en un comentario:

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
Esto solo obtiene atributos de nivel de tipo, no propiedades, campos o miembros, ¿verdad?
Maslow

22
muy agradable, yo personalmente uso una versión más corta de esto, ahora: privado estático IEnumerable <T> GetCustomAttributesIncluyendoBaseInterfaces <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 .: Y su solución refactorizada es más rápida.
mynkow

1
@SimonD valió la pena una respuesta, en lugar de un comentario.
Nick N.

¿Hay alguna razón para no reemplazar Applycon el incorporado ForEachdeMicrosoft.Practices.ObjectBuilder2
Jacob Brewer

29

Un artículo de Brad Wilson sobre esto: ¡Atributos de interfaz! = Atributos de clase

Para resumir: las clases no heredan de las interfaces, las implementan. Esto significa que los atributos no forman parte automáticamente de la implementación.

Si necesita heredar atributos, use una clase base abstracta, en lugar de una interfaz.


¿Qué pasa si tiene varias interfaces que está implementando? No puede simplemente cambiar esas interfaces en clases abstractas porque C # carece de la categoría de herencia múltiple.
Andy

10

Si bien una clase C # no hereda atributos de sus interfaces, existe una alternativa útil al vincular modelos en ASP.NET MVC3.

Si declara que el modelo de la vista es la interfaz en lugar del tipo concreto, entonces la vista y el encuadernador del modelo aplicarán los atributos (por ejemplo, [Required]o [DisplayName("Foo")]desde la interfaz al renderizar y validar el modelo:

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

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

Luego en la vista:

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

Esto es más para personas que buscan extraer atributos de propiedades que pueden existir en una interfaz implementada. Debido a que esos atributos no son parte de la clase, esto le dará acceso a ellos. tenga en cuenta que tengo una clase de contenedor simple que le da acceso a PropertyInfo, ya que para eso lo necesitaba. Hackea como necesites. Esto funcionó bien para mí.

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: esto cubre la herencia de atributos de interfaces en miembros (incluidas propiedades). Hay respuestas simples arriba para las definiciones de tipo. Acabo de publicar esto porque encontré que era una limitación irritante y quería compartir una solución :)

Las interfaces son herencia múltiple y se comportan como herencia en el sistema de tipos. No hay una buena razón para este tipo de cosas. La reflexión es un poco cursi. He añadido comentarios para explicar las tonterías.

(Esto es .NET 3.5 porque resulta que es lo que está usando el proyecto que estoy haciendo en este momento).

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

Prueba 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

Agregue una interfaz con propiedades que tengan atributos / atributos personalizados adjuntos a las mismas propiedades que tiene la clase. Podemos extraer la interfaz de la clase usando la función de refactorización de Visual Studio. Haga que una clase parcial implemente esa interfaz.

Ahora obtenga el objeto "Tipo" del objeto de clase y obtenga atributos personalizados de la información de la propiedad usando getProperties en el objeto Tipo. Esto no dará los atributos personalizados en el objeto de la clase, ya que las propiedades de la clase no tenían los atributos personalizados de las propiedades de la interfaz adjuntos / heredados.

Ahora llame a GetInterface (NameOfImplemetedInterfaceByclass) en el objeto Type de la clase recuperado anteriormente. Esto proporcionará el objeto "Tipo" de la interfaz. debemos conocer el NOMBRE de la interfaz implementada. Desde el objeto Tipo, obtenga información de propiedad y si la propiedad de la interfaz tiene atributos personalizados adjuntos, la información de propiedad proporcionará una lista de atributos personalizados. La clase de implementación debe haber proporcionado la implementación de las propiedades de la interfaz. Haga coincidir el nombre de propiedad específico del objeto de clase dentro de la lista de información de propiedad de la interfaz para obtener la lista de atributos personalizados.

Esto funcionará.


0

Aunque mi respuesta es tardía y específica para un caso determinado, me gustaría agregar algunas ideas. Como se sugiere en otras respuestas, la Reflexión u otros métodos lo harían.

En mi caso, se necesitaba una propiedad (marca de tiempo) en todos los modelos para cumplir con ciertos requisitos (atributo de verificación de concurrencia) en un proyecto principal de Entity Framework. Podríamos agregar [] sobre todas las propiedades de la clase (agregando en la interfaz IModel qué modelos implementaron, no funcionaron). Pero ahorré tiempo a través de Fluent API, que es útil en estos casos. ¡En API fluida, puedo verificar el nombre de propiedad específico en todos los modelos y configurarlo como IsConcurrencyToken () en 1 línea!

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

Del mismo modo, si necesita agregar algún atributo al mismo nombre de propiedad en cientos de clases / modelos, podemos usar métodos api fluidos para resolver atributos incorporados o personalizados. Aunque la API fluida de EF (tanto core como EF6) puede usar la reflexión detrás de escena, podemos ahorrar esfuerzo :)

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.