2009-02-12 12 views
91

Questo sembrerebbe implicare "no". Che è sfortunatoUna classe C# può ereditare attributi dalla sua interfaccia?

[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?

risposta

59

No. Ogni volta che si implementa un'interfaccia o si sovrascrivono membri in una classe derivata, è necessario ripetere la dichiarazione degli attributi.

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

Ad 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 
+0

Sei sicuro di questo? Il metodo MemberInfo.GetCustomAttributes accetta un argomento che indica se la struttura di ereditarietà deve essere cercata. –

+3

Hmm. Ho appena notato che la domanda riguarda l'ereditazione degli attributi da un'interfaccia non da una classe base. –

+0

C'è qualche ragione per mettere gli attributi sulle interfacce, quindi? –

30

È possibile definire un metodo utile 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); 
    } 
} 

Aggiornamento:

Ecco una versione più breve come proposto dal 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 carattere, non proprietà, campi o membri, giusto? – Maslow

+17

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

+1

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

16

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

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

Se è necessario ereditare gli attributi, utilizzare una classe base astratta anziché un'interfaccia.

10

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

Se si dichiara il modello della vista per essere l'interfaccia piuttosto che il tipo di cemento, quindi la vista e il modello di legante si applicano gli attributi (ad esempio, [Required] o [DisplayName("Foo")] dall'interfaccia durante il rendering e la validazione del modello:

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

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

Poi 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) 
2

Questo è più per le persone in cerca di estrarre gli attributi di proprietà che possono esistere su un'interfaccia implementata Perché questi attributi non sono parte del classe, questo ti darà accesso a loro. nota, ho una semplice classe contenitore che ti dà accesso a PropertyInfo - poiché questo è ciò di cui avevo bisogno. Hack up di cui hai bisogno. 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; } 
    } 
} 
Problemi correlati