Una classe C # può ereditare attributi dalla sua interfaccia?


114

Ciò sembrerebbe implicare "no". Che è un peccato.

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

Una classe può ereditare attributi da un'interfaccia? O sto abbaiando sull'albero sbagliato qui?

Risposte:


73

No. Ogni volta che si implementa un'interfaccia o si sovrascrivono i membri in una classe derivata, è necessario dichiarare nuovamente gli attributi.

Se ti interessa solo ComponentModel (non riflesso diretto), esiste un modo ( [AttributeProvider]) per suggerire attributi da un tipo esistente (per evitare la duplicazione), ma è valido solo per l'utilizzo di proprietà e indicizzatore.

Come esempio:

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

uscite:

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

Sei sicuro di questo? Il metodo MemberInfo.GetCustomAttributes accetta un argomento che indica se è necessario eseguire la ricerca nell'albero dell'ereditarietà.
Rune Grimstad

3
Hmm. Ho appena notato che la domanda riguarda l'ereditarietà degli attributi da un'interfaccia non da una classe base.
Rune Grimstad,

C'è qualche motivo per mettere gli attributi sulle interfacce allora?
Ryan Penfold

5
@ Ryan - certo: per aver descritto l'interfaccia. Ad esempio, contratti di servizio.
Marc Gravell

3
Marc (e @Rune): Sì, l'OP riguardava le interfacce. Ma la prima frase della tua risposta potrebbe confondere: "... o sovrascrivere i membri in una classe derivata ..." - questo non è necessariamente vero. Puoi fare in modo che la tua classe erediti gli attributi dalla sua classe di base. Non puoi farlo solo con le interfacce. Vedi anche: stackoverflow.com/questions/12106566/...
chiccodoro

39

Puoi definire un utile metodo di estensione ...

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

Ecco il metodo di estensione:

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

Aggiornare:

Ecco una versione più breve proposta da SimonD in un commento:

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
Questo ottiene solo attributi a livello di tipo, non proprietà, campi o membri, giusto?
Maslow

22
molto carino, io personalmente uso una versione più breve di questo, ora: private static IEnumerable <T> GetCustomAttributesIncludingBaseInterfaces <T> (this Type type) {var attributeType = typeof (T); tipo restituito.GetCustomAttributes (attributeType, true) .Union (type.GetInterfaces (). SelectMany (interfaceType => interfaceType.GetCustomAttributes (attributeType, true))). Distinct (). Cast <T> (); }
Simon D.

1
@ SimonD .: E la tua soluzione refactored è più veloce.
mynkow

1
@ SimonD questo valeva una risposta, invece di un commento.
Nick N.

C'è qualche motivo per non sostituire Applycon il built-in ForEachdaMicrosoft.Practices.ObjectBuilder2
Jacob Brewer

29

Un articolo di Brad Wilson su questo: Interface Attributes! = Class Attributes

Riassumendo: le classi non ereditano dalle interfacce, le implementano. Ciò significa che gli attributi non fanno parte automaticamente dell'implementazione.

Se hai bisogno di ereditare gli attributi, usa una classe base astratta, piuttosto che un'interfaccia.


Cosa succede se si implementano più interfacce? Non puoi semplicemente cambiare quelle interfacce in classi astratte perché C # manca nella categoria di ereditarietà multipla.
Andy

10

Sebbene una classe C # non erediti attributi dalle sue interfacce, esiste un'alternativa utile quando si associano modelli in ASP.NET MVC3.

Se dichiari che il modello della vista è l'interfaccia piuttosto che il tipo concreto, la vista e il raccoglitore del modello applicheranno gli attributi (ad esempio, [Required]o [DisplayName("Foo")]dall'interfaccia durante il rendering e la convalida del modello:

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

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

Quindi nella 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

Questo è più per le persone che cercano di estrarre attributi da proprietà che potrebbero esistere su un'interfaccia implementata. Poiché questi attributi non fanno parte della classe, questo ti darà accesso ad essi. nota, ho una semplice classe contenitore che ti dà accesso a PropertyInfo, poiché è quello per cui avevo bisogno. Hack up come ti serve. Questo ha funzionato bene per me.

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

EDIT: copre l'ereditarietà di attributi dalle interfacce sui membri (incl. Proprietà). Ci sono risposte semplici sopra per le definizioni del tipo. L'ho appena pubblicato perché l'ho trovato un limite irritante e volevo condividere una soluzione :)

Le interfacce sono ereditarietà multipla e si comportano come eredità nel sistema di tipi. Non c'è una buona ragione per questo genere di cose. La riflessione è un po 'stupida. Ho aggiunto commenti per spiegare le sciocchezze.

(Questo è .NET 3.5 perché è proprio quello che sta utilizzando il progetto che sto facendo al 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;
    }
}

Barebones NUnit test

[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

Aggiungi un'interfaccia con proprietà che hanno attributi / attributi personalizzati collegati alle stesse proprietà della classe. Possiamo estrarre l'interfaccia della classe utilizzando la funzionalità di refactoring di Visual Studio. Chiedi a una classe parziale di implementare quell'interfaccia.

Ora ottieni l'oggetto "Tipo" dell'oggetto classe e ottieni attributi personalizzati dalle informazioni sulla proprietà utilizzando getProperties sull'oggetto Tipo. Ciò non fornirà gli attributi personalizzati sull'oggetto classe poiché le proprietà della classe non avevano gli attributi personalizzati delle proprietà dell'interfaccia collegati / ereditati.

Ora chiama GetInterface (NameOfImplemetedInterfaceByclass) sull'oggetto Type della classe recuperato sopra. Ciò fornirà l'oggetto "Tipo" dell'interfaccia. dovremmo conoscere il NOME dell'interfaccia implementata. Dall'oggetto Tipo ottieni informazioni sulla proprietà e se la proprietà dell'interfaccia ha attributi personalizzati allegati, le informazioni sulla proprietà forniranno un elenco di attributi personalizzati. La classe di implementazione deve aver fornito l'implementazione delle proprietà dell'interfaccia. Abbina il nome della proprietà specifica dell'oggetto classe all'interno dell'elenco delle informazioni sulle proprietà dell'interfaccia per ottenere l'elenco degli attributi personalizzati.

Questo funzionerà.


0

Sebbene la mia risposta sia tardiva e specifica per un determinato caso, vorrei aggiungere alcune idee. Come suggerito in altre risposte, la riflessione o altri metodi lo farebbero.

Nel mio caso una proprietà (timestamp) era necessaria in tutti i modelli per soddisfare determinati requisiti (attributo di controllo della concorrenza) in un progetto principale di Entity framework. Potremmo aggiungere [] sopra tutte le proprietà di classe (aggiungendo nell'interfaccia IModel quali modelli implementati non hanno funzionato). Ma ho risparmiato tempo grazie all'API Fluent, utile in questi casi. In un'API fluente, posso verificare il nome di una proprietà specifica in tutti i modelli e impostare come IsConcurrencyToken () in 1 riga !!

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

Allo stesso modo, se è necessario aggiungere un attributo allo stesso nome di proprietà in centinaia di classi / modelli, è possibile utilizzare metodi API fluenti per il risolutore di attributi integrato o personalizzato. Sebbene le API fluenti EF (sia core che EF6) possano utilizzare la riflessione dietro le quinte, possiamo risparmiare fatica :)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.