2009-02-12 12 views
91

Dies würde "nein" implizieren. Was unglücklich ist.Kann eine C# -Klasse Attribute von ihrer Schnittstelle erben?

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

Kann eine Klasse Attribute von einer Schnittstelle erben? Oder belle ich hier den falschen Baum an?

Antwort

59

Nein. Wenn Sie eine Schnittstelle implementieren oder Member in einer abgeleiteten Klasse überschreiben, müssen Sie die Attribute erneut deklarieren.

Wenn Sie nur an ComponentModel interessiert sind (keine direkte Reflektion), gibt es eine Möglichkeit ([AttributeProvider]), Attribute eines vorhandenen Typs vorzuschlagen (um Doppelungen zu vermeiden), aber nur für die Verwendung von Eigenschaften und Indexern.

Als Beispiel:

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

Ausgänge:

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

Sind Sie sich sicher? Die Methode MemberInfo.GetCustomAttributes benötigt ein Argument, das angibt, ob der Vererbungsbaum durchsucht werden soll. –

+3

Hmm. Mir ist gerade aufgefallen, dass es bei der Frage darum geht, Attribute von einer Schnittstelle zu übernehmen, nicht von einer Basisklasse. –

+0

Gibt es einen Grund, Attribute auf Schnittstellen zu setzen? –

30

Sie können eine nützliche Erweiterung Methode definieren ...

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

Hier ist die Extension-Methode:

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

Update:

Hier ist eine kürzere Version, wie in einem Kommentar von Simond vorgeschlagen:

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

Dies erhält nur Attribute auf Typprofilebene, nicht Eigenschaften, Felder oder Member, oder? – Maslow

+17

sehr schön, ich persönlich eine kürzere Version dieses verwenden, jetzt: private static IEnumerable GetCustomAttributesIncludingBaseInterfaces (dieser Typ Typ) {var attribute = typeof (T); Rückgabetyp.GetCustomAttributes (attributeType, true) .Union (type.GetInterfaces(). SelectMany (interfaceType => interfaceType.GetCustomAttributes (attributeType, true))). Distinct(). Cast (); } –

+1

@SimonD .: Und Ihre Refaktorlösung ist schneller. – mynkow

16

Ein Artikel von Brad Wilson dazu: Interface Attributes != Class Attributes

Fassen wir zusammen: Klassen don‘ t sie von Schnittstellen erben, implementieren sie sie. Dies bedeutet, dass die Attribute nicht automatisch Teil der Implementierung sind.

Wenn Sie Attribute erben müssen, verwenden Sie eine abstrakte Basisklasse anstelle einer Schnittstelle.

10

Während eine C# -Klasse keine Attribute von ihren Schnittstellen erbt, gibt es eine nützliche Alternative beim Binden von Modellen in ASP.NET MVC3.

Wenn Sie die Ansicht des Modells erklären eher die Schnittstelle zu sein, als die konkrete Art, dann ist die Ansicht und das Modell Bindemittel werden die Attribute gelten (zB [Required] oder [DisplayName("Foo")] von der Schnittstelle beim Rendern und die Validierung des Modells:

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

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

Dann in der Ansicht.

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

Dieses mehr für die Menschen suchen Attribute von Eigenschaften zu extrahieren, die auf einer implementierten Schnittstelle existieren Da diese Attribute nicht Teil der sind Klasse, das wird Ihnen den Zugang zu ihnen geben. Beachten Sie, dass ich eine einfache Containerklasse habe, die Ihnen Zugriff auf die PropertyInfo gibt - für die ich sie benötigt habe. Hack, wie du brauchst. Das hat gut für mich funktioniert.

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; } 
    } 
} 
Verwandte Themen