119 votes

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

Cela semble impliquer "non". Ce qui est regrettable.

[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 je fais fausse route ?

76voto

Marc Gravell Points 482669

Non. Lorsque vous implémentez une interface ou que vous surchargez des membres dans une classe dérivée, vous devez redéclarer les attributs.

Si vous ne vous intéressez qu'au ComponentModel (et non à la réflexion directe), il existe un moyen ( [AttributeProvider] ) de suggérer des attributs d'un type existant (pour éviter la duplication), mais elle n'est valable que pour l'utilisation des propriétés et des indexeurs.

A titre d'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);
        }
    }
}

sorties :

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

0 votes

Êtes-vous sûr de cela ? La méthode MemberInfo.GetCustomAttributes prend un argument qui indique si l'arbre d'héritage doit être recherché.

3 votes

Hmm. Je viens de remarquer que la question porte sur l'héritage des attributs d'une interface et non d'une classe de base.

0 votes

Y a-t-il une raison de mettre des attributs sur les interfaces alors ?

41voto

tanascius Points 22712

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

Mise à jour :

Voici une version plus courte telle que 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)))
    .Cast<T>();
}

1 votes

Cela ne permet d'obtenir que les attributs de type, et non les propriétés, les champs ou les membres, n'est-ce pas ?

22 votes

Très bien, j'utilise personnellement une version plus courte de ceci, maintenant : private static IEnumerable<T> GetCustomAttributesIncludingBaseInterfaces<T>(this Type) { var attributeType = typeof(T) ; return type. GetCustomAttributes(attributeType, true).Union(type.GetInterfaces().SelectMany(interfaceType => interfaceType.GetCustomAttributes(attributeType, true))).Distinct().Cast<T>() ; }

1 votes

@SimonD. : Et votre solution remaniée est plus rapide.

31voto

Roger Lipscombe Points 34344

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 devez hériter d'attributs, utilisez une classe de base abstraite, plutôt qu'une interface.

2 votes

Que faire si vous mettez en œuvre plusieurs interfaces ? Vous ne pouvez pas simplement transformer ces interfaces en classes abstraites, car le C# ne dispose pas de la catégorie de l'héritage multiple.

11voto

Peter Gluck Points 2913

Bien 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 liant du modèle appliqueront les attributs (par exemple, [Required] o [DisplayName("Foo")] de 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)

4voto

TravisWhidden Points 591

Ceci est plutôt destiné aux personnes qui cherchent à extraire des attributs de propriétés qui peuvent exister sur une interface implémentée. Comme ces attributs ne font pas partie de la classe, ceci vous permettra d'y accéder. Notez que j'ai une classe conteneur simple qui vous donne accès au PropertyInfo - car c'est ce dont j'avais besoin. Modifiez selon vos besoins. Ceci 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; }
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X