Bisakah kelas C # mewarisi atribut dari antarmukanya?

114

Ini sepertinya menyiratkan "tidak". Yang sangat disayangkan.

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

Bisakah kelas mewarisi atribut dari antarmuka? Atau apakah saya menggonggong pohon yang salah di sini?

Roger Lipscombe
sumber

Jawaban:

73

Tidak. Setiap kali mengimplementasikan antarmuka atau mengganti anggota dalam kelas turunan, Anda perlu mendeklarasikan ulang atribut.

Jika Anda hanya peduli tentang ComponentModel (bukan refleksi langsung), ada cara ( [AttributeProvider]) menyarankan atribut dari tipe yang ada (untuk menghindari duplikasi), tetapi ini hanya berlaku untuk penggunaan properti dan pengindeks.

Sebagai contoh:

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

keluaran:

System.SerializableAttribute
System.ComponentModel.AttributeProviderAttribute
System.ComponentModel.EditorAttribute
System.Runtime.InteropServices.ComVisibleAttribute
System.Runtime.InteropServices.ClassInterfaceAttribute
System.ComponentModel.TypeConverterAttribute
System.ComponentModel.MergablePropertyAttribute
Marc Gravell
sumber
Apa kau yakin tentang ini? Metode MemberInfo.GetCustomAttributes mengambil argumen yang memberi tahu apakah pohon warisan harus dicari.
Rune Grimstad
3
Hmm. Saya baru saja memperhatikan bahwa pertanyaannya adalah tentang mewarisi atribut dari antarmuka bukan dari kelas dasar.
Rune Grimstad
Apakah ada alasan untuk menempatkan atribut pada antarmuka?
Ryan Penfold
5
@ Ryan - yakin: untuk menjelaskan antarmuka. Misalnya, kontrak layanan.
Marc Gravell
3
Marc (dan @Rune): Ya, OP adalah tentang antarmuka. Tetapi kalimat pertama dari jawaban Anda mungkin membingungkan: "... atau menimpa anggota di kelas turunan ..." - ini belum tentu benar. Anda dapat membuat kelas Anda mewarisi atribut dari kelas dasarnya. Anda hanya tidak dapat melakukannya dengan antarmuka. Lihat juga: stackoverflow.com/questions/12106566/…
chiccodoro
39

Anda dapat menentukan metode ekstensi yang berguna ...

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

Berikut adalah metode ekstensi:

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

Memperbarui:

Berikut adalah versi yang lebih pendek seperti yang diusulkan oleh SimonD dalam sebuah komentar:

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.dll
sumber
1
Ini hanya mendapatkan atribut tingkat tipe, bukan properti, bidang atau anggota, bukan?
Maslow
22
sangat bagus, saya pribadi menggunakan versi yang lebih pendek ini, sekarang: private static IEnumerable <T> GetCustomAttributesInc IncludedBaseInterfaces <T> (tipe ini) {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 .: Dan solusi refactored Anda lebih cepat.
mynkow
1
@SimonD ini layak mendapat jawaban, bukan komentar.
Nick N.
Apakah ada alasan untuk tidak mengganti Applydengan built in ForEachdariMicrosoft.Practices.ObjectBuilder2
Jacob Brewer
29

Sebuah artikel oleh Brad Wilson tentang ini: Atribut Antarmuka! = Atribut Kelas

Untuk meringkas: kelas tidak mewarisi dari antarmuka, mereka menerapkannya. Artinya, atribut tidak secara otomatis menjadi bagian dari implementasi.

Jika Anda perlu mewarisi atribut, gunakan kelas dasar abstrak, bukan antarmuka.

Roger Lipscombe
sumber
Bagaimana jika Anda memiliki banyak antarmuka yang Anda terapkan? Anda tidak bisa begitu saja mengubah antarmuka tersebut menjadi kelas abstrak karena C # kurang dalam kategori pewarisan ganda.
Andy
10

Sementara kelas C # tidak mewarisi atribut dari antarmukanya, ada alternatif yang berguna saat mengikat model di ASP.NET MVC3.

Jika Anda mendeklarasikan model tampilan sebagai antarmuka dan bukan tipe konkret, maka tampilan dan pengikat model akan menerapkan atribut (misalnya, [Required]atau [DisplayName("Foo")]dari antarmuka saat merender dan memvalidasi model:

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

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

Kemudian dalam tampilan:

@* 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
sumber
4

Ini lebih untuk orang yang ingin mengekstrak atribut dari properti yang mungkin ada pada antarmuka yang diterapkan. Karena atribut tersebut bukan bagian dari kelas, ini akan memberi Anda akses ke sana. perhatikan, saya memiliki kelas kontainer sederhana yang memberi Anda akses ke PropertyInfo - karena untuk itulah saya membutuhkannya. Retas sesuai kebutuhan. Ini bekerja dengan baik untuk saya.

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
sumber
0

EDIT: ini mencakup mewarisi atribut dari antarmuka pada anggota (termasuk properti). Ada jawaban sederhana di atas untuk definisi tipe. Saya baru saja memposting ini karena menurut saya itu batasan yang menjengkelkan dan ingin membagikan solusi :)

Antarmuka adalah beberapa pewarisan dan berperilaku sebagai pewarisan dalam sistem tipe. Tidak ada alasan bagus untuk hal semacam ini. Refleksi agak tipu. Saya telah menambahkan komentar untuk menjelaskan omong kosong tersebut.

(Ini adalah .NET 3.5 karena ini kebetulan yang digunakan oleh proyek yang saya lakukan saat ini.)

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

Tes NUnit Barebones

[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
sumber
0

Tambahkan antarmuka dengan properti yang memiliki atribut / atribut khusus yang dilampirkan ke properti yang sama dengan yang dimiliki kelas. Kita dapat mengekstrak antarmuka kelas dengan menggunakan fitur refactor Visual studio. Minta kelas parsial mengimplementasikan antarmuka itu.

Sekarang Dapatkan objek "Type" dari objek kelas dan dapatkan atribut khusus dari info properti menggunakan getProperties pada objek Type. Ini tidak akan memberikan atribut khusus pada objek kelas karena properti kelas tidak memiliki atribut khusus properti antarmuka yang terpasang / diwariskan.

Sekarang panggil GetInterface (NameOfImplemetedInterfaceByclass) pada objek Type kelas yang diambil di atas. Ini akan memberikan objek "Jenis" antarmuka. kita harus tahu NAMA antarmuka yang diimplementasikan. Dari objek Type dapatkan informasi properti dan jika properti antarmuka memiliki atribut khusus yang dilampirkan maka informasi properti akan memberikan daftar atribut khusus. Kelas pelaksana harus menyediakan implementasi properti antarmuka. Cocokkan nama properti khusus objek kelas dalam daftar informasi properti antarmuka untuk mendapatkan daftar atribut khusus.

Ini akan berhasil.

pengguna11432943
sumber
0

Meskipun jawaban saya terlambat dan spesifik untuk kasus tertentu, saya ingin menambahkan beberapa ide. Seperti yang disarankan dalam jawaban lain, Refleksi atau metode lain akan melakukannya.

Dalam kasus saya, properti (stempel waktu) diperlukan di semua model untuk memenuhi persyaratan tertentu (atribut pemeriksaan konkurensi) dalam proyek inti kerangka kerja Entitas. Kita bisa menambahkan [] di atas semua properti kelas (menambahkan antarmuka IModel yang modelnya diterapkan, tidak berfungsi). Tetapi saya menghemat waktu melalui Fluent API yang berguna dalam kasus ini. Dalam fluent API, saya dapat memeriksa nama properti tertentu di semua model dan ditetapkan sebagai IsConcurrencyToken () dalam 1 baris !!

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

Demikian juga jika Anda memerlukan atribut apa pun untuk ditambahkan ke nama properti yang sama di 100 kelas / model, kita dapat menggunakan metode fluent api untuk inbuilt atau resolver atribut khusus. Meskipun api fasih EF (baik inti dan EF6) dapat menggunakan refleksi di belakang layar, kami dapat menghemat tenaga :)

Prasanna Venkatanathan
sumber