Une classe C # peut-elle hériter des attributs de son interface?

114

Cela semblerait impliquer «non». Ce qui est malheureux.

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

Une classe peut-elle hériter des attributs d'une interface? Ou est-ce que j'aboie le mauvais arbre ici?

Roger Lipscombe
la source

Réponses:

73

Non. Chaque fois que vous implémentez une interface ou remplacez des membres dans une classe dérivée, vous devez re-déclarer les attributs.

Si vous ne vous souciez que de ComponentModel (pas de réflexion directe), il existe un moyen ( [AttributeProvider]) de suggérer des attributs d'un type existant (pour éviter la duplication), mais il n'est valable que pour l'utilisation des propriétés et de l'indexeur.

Par exemple:

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

les sorties:

System.SerializableAttribute
System.ComponentModel.AttributeProviderAttribute
System.ComponentModel.EditorAttribute
System.Runtime.InteropServices.ComVisibleAttribute
System.Runtime.InteropServices.ClassInterfaceAttribute
System.ComponentModel.TypeConverterAttribute
System.ComponentModel.MergablePropertyAttribute
Marc Gravell
la source
Es-tu sûr de ça? La méthode MemberInfo.GetCustomAttributes prend un argument qui indique si l'arborescence d'héritage doit être recherchée.
Rune Grimstad
3
Hmm. Je viens de remarquer que la question concerne l'héritage des attributs d'une interface et non d'une classe de base.
Rune Grimstad le
Y a-t-il alors une raison de mettre des attributs sur les interfaces?
Ryan Penfold
5
@Ryan - sûr: pour décrire l'interface. Par exemple, les contrats de service.
Marc Gravell
3
Marc (et @Rune): Oui, l'OP concernait les interfaces. Mais la première phrase de votre réponse peut prêter à confusion: "... ou des membres supérieurs dans une classe dérivée ..." - ce n'est pas nécessairement vrai. Vous pouvez faire en sorte que votre classe hérite des attributs de sa classe de base. Vous ne pouvez pas faire cela uniquement avec des interfaces. Voir aussi: stackoverflow.com/questions/12106566/…
chiccodoro
39

Vous pouvez définir une méthode d'extension utile ...

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

Voici la méthode d'extension:

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

Mettre à jour:

Voici une version plus courte proposée par SimonD dans un commentaire:

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
la source
1
Cela n'obtient que des attributs au niveau du type, pas des propriétés, des champs ou des membres, n'est-ce pas?
Maslow
22
très sympa, j'utilise personnellement une version plus courte de ceci, maintenant: private static IEnumerable <T> GetCustomAttributesIncludingBaseInterfaces <T> (ce type de type) {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 .: Et votre solution refactorisée est plus rapide.
mynkow
1
@SimonD cela valait une réponse, au lieu d'un commentaire.
Nick N.20
Y a-t-il une raison de ne pas remplacer Applypar le construit ForEachdeMicrosoft.Practices.ObjectBuilder2
Jacob Brewer
29

Un article de Brad Wilson à ce sujet: Attributs d'interface! = Attributs de classe

Pour résumer: les classes n'héritent pas des interfaces, elles les implémentent. Cela signifie que les attributs ne font pas automatiquement partie de l'implémentation.

Si vous avez besoin d'hériter d'attributs, utilisez une classe de base abstraite plutôt qu'une interface.

Roger Lipscombe
la source
Et si vous implémentez plusieurs interfaces? Vous ne pouvez pas simplement modifier ces interfaces en classes abstraites car C # manque dans la catégorie d'héritage multiple.
Andy le
10

Alors qu'une classe C # n'hérite pas des attributs de ses interfaces, il existe une alternative utile lors de la liaison de modèles dans ASP.NET MVC3.

Si vous déclarez que le modèle de la vue est l'interface plutôt que le type concret, alors la vue et le classeur de modèle appliqueront les attributs (par exemple, [Required]ou [DisplayName("Foo")]depuis l'interface lors du rendu et de la validation du modèle:

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

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

Puis dans la vue:

@* 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
la source
4

C'est plus pour les personnes qui cherchent à extraire des attributs de propriétés qui peuvent exister sur une interface implémentée. Parce que ces attributs ne font pas partie de la classe, cela vous donnera accès à eux. note, j'ai une classe de conteneur simple qui vous donne accès à PropertyInfo - car c'est pour cela que j'en avais besoin. Hackez selon vos besoins. Cela a bien fonctionné pour moi.

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; }
    }
}
TravisCaché
la source
0

EDIT: cela couvre l'héritage des attributs des interfaces sur les membres (y compris les propriétés). Il y a des réponses simples ci-dessus pour les définitions de type. Je viens de publier ceci parce que j'ai trouvé que c'était une limitation irritante et que je voulais partager une solution :)

Les interfaces sont un héritage multiple et se comportent comme un héritage dans le système de types. Il n'y a pas une bonne raison pour ce genre de choses. La réflexion est un peu hokey. J'ai ajouté des commentaires pour expliquer le non-sens.

(Il s'agit de .NET 3.5 parce que c'est justement ce que le projet que je fais en ce moment utilise.)

// 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
la source
0

Ajoutez une interface avec des propriétés qui ont des attributs / attributs personnalisés attachés aux mêmes propriétés que la classe. Nous pouvons extraire l'interface de la classe en utilisant la fonction de refactor de Visual studio. Demandez à une classe partielle d'implémenter cette interface.

Obtenez maintenant l'objet "Type" de l'objet de classe et obtenez des attributs personnalisés à partir des informations de propriété à l'aide de getProperties sur l'objet Type. Cela ne donnera pas les attributs personnalisés sur l'objet de classe car les propriétés de classe n'avaient pas les attributs personnalisés des propriétés d'interface attachés / hérités.

Appelez maintenant GetInterface (NameOfImplemetedInterfaceByclass) sur l'objet Type de la classe récupéré ci-dessus. Cela fournira l'objet "Type" de l'interface. nous devrions connaître le NOM de l'interface implémentée. À partir de l'objet Type, obtenez les informations de propriété et si la propriété de l'interface a des attributs personnalisés attachés, les informations de propriété fourniront une liste d'attributs personnalisés. La classe d'implémentation doit avoir fourni l'implémentation des propriétés de l'interface. Faites correspondre le nom de propriété spécifique de l'objet de classe dans la liste des informations de propriété de l'interface pour obtenir la liste des attributs personnalisés.

Cela fonctionnera.

user11432943
la source
0

Bien que ma réponse soit tardive et spécifique à un cas particulier, j'aimerais ajouter quelques idées. Comme suggéré dans d'autres réponses, la réflexion ou d'autres méthodes le feraient.

Dans mon cas, une propriété (horodatage) était nécessaire dans tous les modèles pour répondre à certaines exigences (attribut de vérification de la concurrence) dans un projet de base du framework Entity. Nous pourrions soit ajouter [] au-dessus de toutes les propriétés de classe (l'ajout dans l'interface IModel quels modèles implémentés ne fonctionnaient pas). Mais j'ai gagné du temps grâce à l'API Fluent, ce qui est utile dans ces cas. Dans l'API fluide, je peux vérifier le nom de propriété spécifique dans tous les modèles et définir comme IsConcurrencyToken () en 1 ligne !!

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

De même, si vous avez besoin d'un attribut à ajouter au même nom de propriété dans des centaines de classes / modèles, nous pouvons utiliser des méthodes API fluides pour le résolveur d'attributs intégré ou personnalisé. Bien que l'API fluente EF (à la fois core et EF6) puisse utiliser la réflexion en arrière-plan, nous pouvons économiser des efforts :)

Prasanna Venkatanathan
la source