편집 : 이것은 멤버 (속성 포함)의 인터페이스에서 상속 속성을 다룹니다. 유형 정의에 대한 위의 간단한 답변이 있습니다. 나는 그것이 짜증나는 제한이라는 것을 알았고 해결책을 공유하고 싶었 기 때문에 방금 게시했습니다. :)
인터페이스는 다중 상속이며 유형 시스템에서 상속으로 작동합니다. 이런 종류의 일에는 타당한 이유가 없습니다. 반사는 약간 하키입니다. 나는 말도 안되는 것을 설명하기 위해 주석을 추가했습니다.
(이것은 .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");
}
}