Czy klasa C # może dziedziczyć atrybuty z interfejsu?

114

Wydaje się, że oznacza to „nie”. Co jest niefortunne.

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

Czy klasa może dziedziczyć atrybuty z interfejsu? A może szczekam tutaj na niewłaściwe drzewo?

Roger Lipscombe
źródło

Odpowiedzi:

73

Nie. Za każdym razem, gdy implementujesz interfejs lub zastępujesz elementy składowe w klasie pochodnej, musisz ponownie zadeklarować atrybuty.

Jeśli zależy Ci tylko na ComponentModel (a nie na bezpośrednim odbiciu), istnieje sposób ( [AttributeProvider]) sugerowania atrybutów z istniejącego typu (w celu uniknięcia duplikacji), ale jest on ważny tylko w przypadku użycia właściwości i indeksatora.

Jako przykład:

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

wyjścia:

System.SerializableAttribute
System.ComponentModel.AttributeProviderAttribute
System.ComponentModel.EditorAttribute
System.Runtime.InteropServices.ComVisibleAttribute
System.Runtime.InteropServices.ClassInterfaceAttribute
System.ComponentModel.TypeConverterAttribute
System.ComponentModel.MergablePropertyAttribute
Marc Gravell
źródło
Jesteś tego pewien? Metoda MemberInfo.GetCustomAttributes przyjmuje argument, który mówi, czy należy przeszukiwać drzewo dziedziczenia.
Rune Grimstad
3
Hmm. Właśnie zauważyłem, że pytanie dotyczy dziedziczenia atrybutów z interfejsu, a nie z klasy bazowej.
Rune Grimstad
Czy jest zatem jakiś powód, aby umieszczać atrybuty w interfejsach?
Ryan Penfold
5
@Ryan - jasne: do opisu interfejsu. Na przykład umowy serwisowe.
Marc Gravell
3
Marc (i @Rune): Tak, operacja dotyczyła interfejsów. Ale pierwsze zdanie twojej odpowiedzi może być mylące: „… lub nadpisywanie elementów w klasie pochodnej…” - to niekoniecznie jest prawdą. Twoja klasa może dziedziczyć atrybuty z klasy bazowej. Nie możesz tego zrobić tylko z interfejsami. Zobacz też: stackoverflow.com/questions/12106566/…
chiccodoro
39

Możesz zdefiniować przydatną metodę rozszerzenia ...

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

Oto metoda rozszerzenia:

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

Aktualizacja:

Oto krótsza wersja zaproponowana przez SimonD w komentarzu:

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>();
}
tanascius
źródło
1
Pobiera to tylko atrybuty na poziomie typu, a nie właściwości, pola lub elementy członkowskie, prawda?
Maslow
22
bardzo fajnie, osobiście używam krótszej wersji tego, teraz: private static IEnumerable <T> GetCustomAttributesIncludeBaseInterfaces <T> (ten typ) {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 .: A Twoje rozwiązanie refaktoryzowane jest szybsze.
mynków
1
@SimonD to było warte odpowiedzi zamiast komentarza.
Nick N.
Czy jest jakiś powód, aby nie zastępować Applywbudowanym ForEachodMicrosoft.Practices.ObjectBuilder2
Jacob Brewer
29

Artykuł autorstwa Brada Wilsona na ten temat: Atrybuty interfejsu! = Atrybuty klas

Podsumowując: klasy nie dziedziczą po interfejsach, tylko je implementują. Oznacza to, że atrybuty nie są automatycznie częścią implementacji.

Jeśli musisz dziedziczyć atrybuty, użyj abstrakcyjnej klasy bazowej zamiast interfejsu.

Roger Lipscombe
źródło
A co, jeśli masz wiele implementowanych interfejsów? Nie można po prostu zmienić tych interfejsów w klasy abstrakcyjne, ponieważ C # brakuje kategorii dziedziczenia wielokrotnego.
Andy
10

Chociaż klasa C # nie dziedziczy atrybutów ze swoich interfejsów, istnieje przydatna alternatywa podczas wiązania modeli w ASP.NET MVC3.

Jeśli zadeklarujesz model widoku jako interfejs, a nie konkretny typ, widok i spinacz modelu zastosują atrybuty (np. [Required]Lub [DisplayName("Foo")]z interfejsu podczas renderowania i walidacji modelu:

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

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

Następnie w widoku:

@* 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)
Peter Gluck
źródło
4

Jest to bardziej dla osób, które chcą wyodrębnić atrybuty z właściwości, które mogą istnieć w zaimplementowanym interfejsie. Ponieważ te atrybuty nie są częścią klasy, daje to dostęp do nich. uwaga, mam prostą klasę kontenera, która daje dostęp do PropertyInfo - ponieważ do tego potrzebowałem. Hakuj tak, jak potrzebujesz. To działało dobrze dla mnie.

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; }
    }
}
TravisWhidden
źródło
0

EDYCJA: obejmuje dziedziczenie atrybutów z interfejsów na elementach (w tym właściwości). Powyżej znajdują się proste odpowiedzi dotyczące definicji typów. Właśnie to opublikowałem, ponieważ uznałem to za irytujące ograniczenie i chciałem podzielić się rozwiązaniem :)

Interfejsy są dziedziczone wielokrotnie i zachowują się jak dziedziczenie w systemie typów. Nie ma dobrego powodu dla tego rodzaju rzeczy. Odbicie jest trochę szalone. Dodałem komentarze, aby wyjaśnić bzdury.

(To jest .NET 3.5, ponieważ tak się składa, że ​​właśnie tego używa projekt, którego obecnie używam).

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

Test 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");
    }
}
Seth
źródło
0

Dodaj interfejs z właściwościami, które mają atrybuty / atrybuty niestandardowe dołączone do tych samych właściwości, które ma klasa. Możemy wyodrębnić interfejs klasy za pomocą funkcji refaktoryzacji programu Visual Studio. Niech część klasy implementuje ten interfejs.

Teraz pobierz obiekt „Type” obiektu klasy i uzyskaj niestandardowe atrybuty z informacji o właściwościach za pomocą funkcji getProperties w obiekcie Type. Nie spowoduje to nadania atrybutów niestandardowych w obiekcie klasy, ponieważ właściwości klasy nie miały dołączonych / odziedziczonych atrybutów niestandardowych właściwości interfejsu.

Teraz wywołaj GetInterface (NameOfImplemetedInterfaceByclass) na pobranym powyżej obiekcie Type klasy. Zapewni to obiekt „Type” interfejsu. powinniśmy znać NAZWĘ zaimplementowanego interfejsu. Z obiektu Type pobierz informacje o właściwościach, a jeśli właściwość interfejsu ma dołączone atrybuty niestandardowe, informacje o właściwości będą zawierać listę atrybutów niestandardowych. Klasa implementująca musi mieć implementację właściwości interfejsu. Dopasuj nazwę właściwości obiektu klasy na liście informacji o właściwościach interfejsu, aby uzyskać listę atrybutów niestandardowych.

To zadziała.

user11432943
źródło
0

Chociaż moja odpowiedź jest spóźniona i dotyczy konkretnego przypadku, chciałbym dodać kilka pomysłów. Jak sugerowano w innych odpowiedziach, wystarczyłoby Refleksja lub inne metody.

W moim przypadku właściwość (sygnatura czasowa) była potrzebna we wszystkich modelach, aby spełnić określone wymaganie (atrybut sprawdzania współbieżności) w podstawowym projekcie Entity Framework. Mogliśmy albo dodać [] przede wszystkim właściwości klasy (dodanie w interfejsie IModel, który model jest zaimplementowany, nie działa). Ale zaoszczędziłem czas dzięki Fluent API, które jest pomocne w takich przypadkach. Korzystając z płynnego API, mogę sprawdzić konkretną nazwę właściwości we wszystkich modelach i ustawić jako IsConcurrencyToken () w 1 linii !!

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

Podobnie, jeśli potrzebujesz dodać atrybut do tej samej nazwy właściwości w setkach klas / modeli, możemy użyć metod Fluent API dla wbudowanego lub niestandardowego rozpoznawania atrybutów. Chociaż EF (zarówno rdzeń, jak i EF6) Fluent API może używać refleksji za kulisami, możemy zaoszczędzić wysiłku :)

Prasanna Venkatanathan
źródło