C # 클래스가 인터페이스에서 속성을 상속 할 수 있습니까?


114

이것은 "아니오"를 의미하는 것처럼 보입니다. 불행한 일입니다.

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

클래스가 인터페이스에서 속성을 상속 할 수 있습니까? 아니면 여기서 잘못된 나무를 짖는 건가요?

답변:


73

아니요. 인터페이스를 구현하거나 파생 클래스에서 멤버를 재정의 할 때마다 특성을 다시 선언해야합니다.

ComponentModel (직접 반사가 아님)에만 관심이 있다면 방법이 있습니다 ([AttributeProvider] 기존 유형의 속성을 제안 )이 있지만 (중복을 방지하기 위해) 속성 및 인덱서 사용에만 유효합니다.

예로서:

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

출력 :

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

이거 확실하니? MemberInfo.GetCustomAttributes 메서드는 상속 트리를 검색해야하는지 여부를 알려주는 인수를 사용합니다.
Rune Grimstad

3
흠. 질문은 기본 클래스가 아닌 인터페이스에서 속성을 상속하는 것에 관한 것입니다.
Rune Grimstad 2009.02.12

그렇다면 인터페이스에 속성을 넣을 이유가 있습니까?
Ryan Penfold

5
@Ryan-sure : 인터페이스를 설명합니다. 예를 들어, 서비스 계약.
Marc Gravell

3
Marc (및 @Rune) : 네, OP는 인터페이스에 관한 것이 었습니다. 그러나 대답의 첫 번째 문장은 혼란 스러울 수 있습니다. "... 또는 파생 클래스에서 멤버 재정의 ..."-반드시 사실은 아닙니다. 클래스가 기본 클래스에서 속성을 상속하도록 할 수 있습니다. 인터페이스만으로는 할 수 없습니다. 참조 : stackoverflow.com/questions/12106566/…
chiccodoro

39

유용한 확장 방법을 정의 할 수 있습니다.

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

확장 방법은 다음과 같습니다.

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

최신 정보:

다음은 SimonD가 의견에서 제안한 짧은 버전입니다.

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
속성, 필드 또는 멤버가 아닌 유형 수준 속성 만 가져옵니다.
Maslow 2011 년

22
아주 좋습니다. 저는 개인적으로 더 짧은 버전을 사용합니다. 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> (); }
Simon D.

1
@SimonD .: 리팩토링 된 솔루션이 더 빠릅니다.
mynkow

1
@SimonD 이것은 댓글 대신 대답할만한 가치가 있습니다.
Nick N.

대체 할 수없는 어떤 이유가 있습니까 Apply내장과 ForEach에서Microsoft.Practices.ObjectBuilder2
야곱 브루

29

이에 대한 Brad Wilson의 기사 : Interface Attributes! = Class Attributes

요약하자면, 클래스는 인터페이스에서 상속하지 않고 구현합니다. 이는 속성이 자동으로 구현의 일부가 아님을 의미합니다.

속성을 상속해야하는 경우 인터페이스가 아닌 추상 기본 클래스를 사용하십시오.


구현중인 인터페이스가 여러 개인 경우 어떻게됩니까? C #에는 다중 상속 범주가 없기 때문에 이러한 인터페이스를 추상 클래스로 변경할 수 없습니다.
Andy

10

C # 클래스는 인터페이스에서 특성을 상속하지 않지만 ASP.NET MVC3에서 모델을 바인딩 할 때 유용한 대안이 있습니다.

당신은 인터페이스가 아닌 구체적인 유형, 다음 뷰와 특성 (예를 들어, 적용 모델 바인더로 뷰의 모델을 선언하는 경우 [Required]또는 [DisplayName("Foo")]인터페이스에서 렌더링 및 모델 검증 :

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

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

그런 다음보기에서 :

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

이것은 구현 된 인터페이스에 존재할 수있는 속성에서 속성을 추출하려는 사람들을위한 것입니다. 이러한 속성은 클래스의 일부가 아니기 때문에 액세스 할 수 있습니다. 참고로 PropertyInfo에 대한 액세스를 제공하는 간단한 컨테이너 클래스가 있습니다. 필요에 따라 해킹하세요. 이것은 나를 위해 잘 작동했습니다.

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

편집 : 이것은 멤버 (속성 포함)의 인터페이스에서 상속 속성을 다룹니다. 유형 정의에 대한 위의 간단한 답변이 있습니다. 나는 그것이 짜증나는 제한이라는 것을 알았고 해결책을 공유하고 싶었 기 때문에 방금 게시했습니다. :)

인터페이스는 다중 상속이며 유형 시스템에서 상속으로 작동합니다. 이런 종류의 일에는 타당한 이유가 없습니다. 반사는 약간 하키입니다. 나는 말도 안되는 것을 설명하기 위해 주석을 추가했습니다.

(이것은 .NET 3.5입니다. 왜냐하면 이것은 현재 제가하고있는 프로젝트가 사용하고있는 것이기 때문입니다.)

// 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 테스트

[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

클래스와 동일한 속성에 연결된 속성 / 사용자 지정 속성이있는 속성이있는 인터페이스를 추가합니다. Visual Studio 리팩터링 기능을 사용하여 클래스의 인터페이스를 추출 할 수 있습니다. 부분 클래스가 해당 인터페이스를 구현하도록하십시오.

이제 클래스 개체의 "Type"개체를 가져오고 Type 개체의 getProperties를 사용하여 속성 정보에서 사용자 지정 특성을 가져옵니다. 이것은 클래스 속성에 인터페이스 속성의 사용자 지정 특성이 연결 / 상속되지 않았기 때문에 클래스 개체에 사용자 지정 특성을 제공하지 않습니다.

이제 위에서 검색 한 클래스의 Type 개체에 대해 GetInterface (NameOfImplemetedInterfaceByclass)를 호출합니다. 이것은 인터페이스의 "Type"객체를 제공합니다. 구현 된 인터페이스의 NAME을 알아야합니다. 유형 개체에서 속성 정보를 가져오고 인터페이스의 속성에 사용자 지정 특성이 첨부되어 있으면 속성 정보가 사용자 지정 특성 목록을 제공합니다. 구현 클래스는 인터페이스 속성의 구현을 제공해야합니다. 인터페이스의 속성 정보 목록 내에서 클래스 개체의 특정 속성 이름을 일치시켜 사용자 지정 속성 목록을 가져옵니다.

작동합니다.


0

내 대답은 늦고 특정 경우에 구체적이지만 몇 가지 아이디어를 추가하고 싶습니다. 다른 답변에서 제안했듯이 Reflection 또는 다른 방법이 그것을 할 것입니다.

필자의 경우 Entity 프레임 워크 핵심 프로젝트의 특정 요구 사항 (동시성 검사 특성)을 충족하기 위해 모든 모델에 속성 (타임 스탬프)이 필요했습니다. 모든 클래스 속성 위에 []를 추가 할 수 있습니다 (모델이 구현되었지만 작동하지 않는 IModel 인터페이스에 추가). 하지만 이러한 경우에 도움이되는 Fluent API를 통해 시간을 절약했습니다. 유창한 API에서는 모든 모델에서 특정 속성 이름을 확인하고 한 줄에 IsConcurrencyToken ()으로 설정할 수 있습니다 !!

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

마찬가지로 100 개의 클래스 / 모델에서 동일한 속성 이름에 속성을 추가해야하는 경우 내장형 또는 사용자 지정 속성 해석기에 유창한 API 메서드를 사용할 수 있습니다. EF (코어와 EF6 모두) 유창한 API는 배후에서 리플렉션을 사용할 수 있지만 노력을 절약 할 수 있습니다. :)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.