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 ?

0voto

Seth Points 450

EDIT : ceci 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. J'ai juste posté ceci parce que j'ai trouvé que c'était une limitation irritante et je voulais partager une solution :)

Les interfaces sont de l'héritage multiple et se comportent comme de l'héritage dans le système de type. Il n'y a pas de bonne raison pour ce genre de choses. Reflection est un peu hokey. J'ai ajouté des commentaires pour expliquer ces absurdités.

(Il s'agit de .NET 3.5 car il se trouve que c'est ce qu'utilise le projet sur lequel je travaille en ce moment).

// 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 NUnit de base

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

0voto

user11432943 Points 1

Ajouter une interface avec des propriétés qui ont des 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 fonctionnalité de refactor de Visual studio. Avoir une classe partielle qui implémente cette interface.

Maintenant, obtenez l'objet "Type" de l'objet classe et récupérez les attributs personnalisés à partir des informations de propriété en utilisant getProperties sur l'objet Type. Cela ne donnera pas les attributs personnalisés sur l'objet classe car les propriétés de la classe n'ont pas les attributs personnalisés des propriétés de l'interface attachés/hérités.

Appelez maintenant GetInterface(NameOfImplemetedInterfaceByclass) sur l'objet Type de la classe récupéré ci-dessus. Cela va Cela fournira l'objet "Type" de l'interface. Nous devrions connaître le NOM de l'interface implémentée. A 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, alors les informations de propriété fourniront la liste des 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 la 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.

Ça va marcher.

0voto

Bien que ma réponse soit tardive et spécifique à un certain cas, je voudrais ajouter quelques idées. Comme suggéré dans d'autres réponses, la réflexion ou d'autres méthodes feraient l'affaire.

Dans mon cas, une propriété (timestamp) était nécessaire dans tous les modèles pour répondre à une certaine exigence (attribut de contrôle de la concurrence) dans un projet Entity framework core. Nous pouvions soit ajouter [] au-dessus de toutes les propriétés de classe (l'ajout dans l'interface IModel que les modèles ont mis en œuvre, n'a pas fonctionné). Mais j'ai gagné du temps grâce à l'API fluente qui est utile dans ces cas. Dans l'API Fluent, je peux vérifier le nom d'une propriété spécifique dans tous les modèles et la 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 qu'un attribut soit ajouté au même nom de propriété dans des centaines de classes/modèles, nous pouvons utiliser les méthodes de l'API fluide pour le résolveur d'attributs intégré ou personnalisé. Bien que l'api fluide d'EF (à la fois le noyau et EF6) puisse utiliser la réflexion dans les coulisses, nous pouvons économiser des efforts :)

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