Bir C # sınıfı, öznitelikleri arayüzünden devralabilir mi?


114

Bu "hayır" anlamına geliyor gibi görünüyor. Bu talihsiz bir durum.

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

Bir sınıf, bir arabirimden öznitelikleri devralabilir mi? Yoksa burada yanlış ağaca mı havlıyorum?

Yanıtlar:


73

Hayır. Bir türetilmiş sınıfta bir arabirim uygularken veya üyeleri geçersiz kılarken, öznitelikleri yeniden bildirmeniz gerekir.

Yalnızca ComponentModel ile ilgileniyorsanız (doğrudan yansıma değil), [AttributeProvider]mevcut bir türden öznitelik önermenin bir yolu ( ) vardır (yinelemeyi önlemek için), ancak bu yalnızca özellik ve dizinleyici kullanımı için geçerlidir.

Örnek olarak:

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

çıktılar:

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

Bundan emin misin? MemberInfo.GetCustomAttributes yöntemi, miras ağacının aranması gerektiğini söyleyen bir bağımsız değişken alır.
Rune Grimstad

3
Hmm. Sorunun temel sınıftan değil bir arayüzden öznitelikleri miras almakla ilgili olduğunu fark ettim.
Rune Grimstad

Öyleyse arayüzlere nitelik eklemek için herhangi bir sebep var mı?
Ryan Penfold

5
@Ryan - tabii: arayüzü açıklamak için. Örneğin, hizmet sözleşmeleri.
Marc Gravell

3
Marc (ve @Rune): Evet, OP arayüzlerle ilgiliydi. Ancak cevabınızın ilk cümlesi kafa karıştırıcı olabilir: "... veya türetilmiş bir sınıftaki üyeleri geçersiz kılmak ..." - bu mutlaka doğru değildir. Sınıfınızın, temel sınıfından öznitelikleri devralmasını sağlayabilirsiniz. Bunu sadece arayüzlerle yapamazsınız. Ayrıca bakınız: stackoverflow.com/questions/12106566/…
chiccodoro

39

Kullanışlı bir uzatma yöntemi tanımlayabilirsiniz ...

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

İşte uzatma yöntemi:

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

Güncelleme:

SimonD tarafından bir yorumda önerildiği üzere daha kısa bir versiyon:

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
Bu sadece tür düzeyinde öznitelikleri alır, özellikleri, alanları veya üyeleri değil, değil mi?
Maslow

22
çok güzel, ben şahsen bunun daha kısa bir sürümünü kullanıyorum, şimdi: private static IEnumerable <T> GetCustomAttributesIncludingBaseInterfaces <T> (bu Type türü) {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 .: Ve yeniden düzenlenen çözümünüz daha hızlı.
mynkow

1
@SimonD bu bir yorum yerine cevaba değerdi.
Nick N.

Yerine herhangi bir neden var mı Applyyerleşik ForEachdenMicrosoft.Practices.ObjectBuilder2
Jacob Brewer

29

Brad Wilson tarafından bununla ilgili bir makale: Interface Attributes! = Class Attributes

Özetlemek gerekirse: sınıflar arayüzlerden miras almaz, onları uygularlar. Bu, özniteliklerin otomatik olarak uygulamanın bir parçası olmadığı anlamına gelir.

Öznitelikleri devralmanız gerekiyorsa, arabirim yerine soyut bir temel sınıf kullanın.


Ya uyguladığınız birden fazla arayüze sahipseniz? Bu arayüzleri soyut sınıflara değiştiremezsiniz çünkü C # çoklu miras kategorisinde eksiktir.
Andy

10

Bir C # sınıfı, arabirimlerinden öznitelikleri devralmazken, ASP.NET MVC3'te modelleri bağlarken yararlı bir alternatif vardır.

Eğer arayüz yerine beton türü, ardından görünüm ve özelliklerini (örneğin geçerli olacaktır modeli bağlayıcı olmasını görünümün modelini bildirirseniz [Required]veya [DisplayName("Foo")]arayüzden render ve modelini doğrulama:

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

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

Ardından görünümde:

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

4

Bu, uygulanan bir arayüzde var olabilecek özelliklerden öznitelikleri çıkarmak isteyen kişiler için daha fazladır. Bu nitelikler sınıfın bir parçası olmadığından, bu size onlara erişim sağlayacaktır. Not, PropertyInfo'ya erişmenizi sağlayan basit bir konteyner sınıfım var - çünkü buna ihtiyacım vardı. İhtiyaç duyduğunuz kadar hackleyin. Bu benim için iyi çalıştı.

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

0

DÜZENLEME: Bu, üyeler üzerindeki arayüzlerden öznitelikleri devralmayı kapsar (özellikler dahil). Tip tanımları için yukarıda basit cevaplar var. Bunu az önce gönderdim çünkü rahatsız edici bir sınırlama olduğunu gördüm ve bir çözüm paylaşmak istedim :)

Arayüzler çoklu kalıtımdır ve tip sisteminde kalıtım gibi davranır. Bu tür şeyler için iyi bir sebep yok. Düşünme biraz hokeydir. Saçmalığı açıklamak için yorumlar ekledim.

(Bu .NET 3.5 çünkü şu anda yaptığım projenin kullandığı şey bu.)

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

Barebones NUnit testi

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

0

Sınıfın sahip olduğu aynı özelliklere eklenmiş özniteliklere / özel özniteliklere sahip özelliklere sahip arabirim ekleyin. Visual studio refactor özelliğini kullanarak sınıfın arayüzünü çıkarabiliriz. Kısmi bir sınıfın bu arabirimi uygulamasına sahip olun.

Şimdi sınıf nesnesinin "Type" nesnesini alın ve Type nesnesinde getProperties kullanarak özellik bilgisinden özel nitelikler alın. Sınıf özellikleri, arabirim özelliklerinin eklenmiş / devralınan özel özniteliklerine sahip olmadığından, bu, sınıf nesnesindeki özel öznitelikleri vermez.

Şimdi yukarıda alınan sınıfın Type nesnesinde GetInterface (NameOfImplemetedInterfaceByclass) çağırın. Bu, arayüzün "Tip" nesnesini sağlayacaktır. uygulanan arayüzün NAME bilgisini bilmeliyiz. Tür nesnesinden özellik bilgilerini alın ve arabirimin özelliğine eklenmiş herhangi bir özel öznitelik varsa, özellik bilgileri özel öznitelik listesi sağlayacaktır. Uygulama sınıfı, arayüz özelliklerinin uygulanmasını sağlamış olmalıdır. Özel öznitelikler listesini almak için arabirimin özellik bilgileri listesinde sınıf nesnesinin belirli özellik adını eşleştirin.

Bu çalışacak.


0

Cevabım geç ve belirli bir duruma özgü olsa da, bazı fikirler eklemek isterim. Diğer cevaplarda önerildiği gibi, Düşünme veya diğer yöntemler bunu yapacaktır.

Benim durumumda, bir Varlık çerçevesi çekirdek projesinde belirli bir gereksinimi (eşzamanlılık denetimi özniteliği) karşılamak için tüm modellerde bir özelliğe (zaman damgası) ihtiyaç duyuldu. Ya tüm sınıf özelliklerinin üzerine [] ekleyebilirdik (hangi modellerin uygulandığı, çalışmayan IModel arayüzüne eklemek). Ancak bu durumlarda yardımcı olan Fluent API ile zamandan tasarruf ettim. Akıcı API'de, tüm modellerde belirli özellik adını kontrol edebilir ve 1 satırda IsConcurrencyToken () olarak ayarlayabilirim !!

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

Benzer şekilde, 100'lerce sınıf / modelde aynı özellik adına eklenecek herhangi bir özniteliğe ihtiyacınız varsa, dahili veya özel öznitelik çözümleyici için akıcı api yöntemlerini kullanabiliriz. EF (hem çekirdek hem de EF6) akıcı API arka planda yansıma kullanabilir, ancak çabadan tasarruf edebiliriz :)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.