programing

속성 대신 마커 인터페이스를 사용하는 설득력있는 이유

randomtip 2021. 1. 16. 09:30
반응형

속성 대신 마커 인터페이스를 사용하는 설득력있는 이유


마커 인터페이스 (멤버가없는 인터페이스)보다 속성을 선호해야한다는 점 은 스택 오버플에서 이전에 논의 되었습니다 . MSDN의 인터페이스 디자인 문서 에서도이 권장 사항을 주장합니다.

마커 인터페이스 (멤버가없는 인터페이스)를 사용하지 마십시오.

사용자 정의 속성은 유형을 표시하는 방법을 제공합니다. 사용자 지정 특성에 대한 자세한 내용은 사용자 지정 특성 작성을 참조하십시오. 코드가 실행될 때까지 속성 검사를 연기 할 수있는 경우 사용자 지정 속성이 선호됩니다. 시나리오에 컴파일 시간 검사가 필요한 경우이 지침을 준수 할 수 없습니다.

이 권장 사항을 적용 하는 FxCop 규칙 도 있습니다 .

빈 인터페이스 방지

인터페이스는 동작 또는 사용 계약을 제공하는 멤버를 정의합니다. 인터페이스에서 설명하는 기능은 상속 계층 구조에서 유형이 나타나는 위치에 관계없이 모든 유형에서 채택 할 수 있습니다. 유형은 인터페이스의 멤버에 대한 구현을 제공하여 인터페이스를 구현합니다. 빈 인터페이스는 구성원을 정의하지 않으므로 구현할 수있는 계약을 정의하지 않습니다.

디자인에 유형이 구현할 것으로 예상되는 빈 인터페이스가 포함 된 경우 인터페이스를 마커로 사용하거나 유형 그룹을 식별하는 방법 일 수 있습니다. 이 식별이 런타임에 발생하는 경우이를 수행하는 올바른 방법은 사용자 정의 속성을 사용하는 것입니다. 속성의 유무 또는 속성의 속성을 사용하여 대상 유형을 식별하십시오. 컴파일 타임에 식별해야하는 경우 빈 인터페이스를 사용할 수 있습니다.

이 기사에서는 경고를 무시할 수있는 한 가지 이유, 즉 유형에 대한 컴파일 시간 식별이 필요할 때만 설명합니다. (이것은 인터페이스 디자인 기사와 일치합니다).

인터페이스가 컴파일 타임에 유형 집합을 식별하는 데 사용되는 경우이 규칙에서 경고를 제외하는 것이 안전합니다.

실제 질문은 다음과 같습니다. Microsoft는 프레임 워크 클래스 라이브러리 설계에서 자체 권장 사항을 따르지 않았습니다 (적어도 몇 가지 경우) : IRequiresSessionState 인터페이스IReadOnlySessionState 인터페이스 . 이러한 인터페이스는 ASP.NET 프레임 워크에서 특정 처리기에 대해 세션 상태를 활성화해야하는지 여부를 확인하는 데 사용됩니다. 당연히 컴파일 타임 타입의 식별에는 사용되지 않습니다. 왜 그렇게하지 않았습니까? 두 가지 잠재적 인 이유를 생각할 수 있습니다.

  1. 마이크로 최적화 : 객체가 인터페이스 ( obj is IReadOnlySessionState)를 구현하는지 확인하는 것이 속성 ( type.IsDefined(typeof(SessionStateAttribute), true)) 을 확인하기 위해 리플렉션을 사용하는 것보다 빠릅니다 . 대부분의 경우 차이는 무시할 수 있지만 실제로는 ASP.NET 런타임의 성능에 중요한 코드 경로에 중요 할 수 있습니다. 그러나 각 처리기 유형에 대한 결과를 캐싱하는 것과 같이 사용할 수있는 해결 방법이 있습니다. 흥미로운 것은 (비슷한 성능 특성이 적용됩니다) ASMX 웹 서비스를 실제로 사용하는 것입니다 EnableSession부동산WebMethod속성을 이 목적을 위해.

  2. 인터페이스 구현은 타사 .NET 언어의 특성으로 유형을 장식하는 것보다 지원 될 가능성이 더 높습니다. ASP.NET은 언어에 구애받지 않도록 설계되었으며 ASP.NET은 지시문특성을 기반으로 해당 인터페이스를 구현하는 유형 ( CodeDom 의 도움을 받아 타사 언어로 가능)에 대한 코드를 생성 하므로 더 많은 것을 만들 수 있습니다. 속성 대신 인터페이스를 사용하는 것이 좋습니다.EnableSessionState<%@ Page %>

속성 대신 마커 인터페이스를 사용하는 설득력있는 이유는 무엇입니까?

이것은 단순히 (미숙 한?) 최적화 또는 프레임 워크 디자인의 작은 실수입니까? (그들은 반사가 "빨간 눈을 가진 큰 괴물"이라고 생각합니까?) 생각?


일반적으로 "마커 인터페이스" 는 파생 형식의 표시해제 할 수 없기 때문에 피 합니다. 그러나 그 외에도 마커 인터페이스가 내장 메타 데이터 지원보다 선호되는 특정 사례는 다음과 같습니다.

  1. 런타임 성능에 민감한 상황.
  2. 주석 또는 속성을 지원하지 않는 언어와의 호환성.
  3. 관심있는 코드가 메타 데이터에 액세스 할 수없는 컨텍스트입니다.
  4. 일반 제약 조건 및 일반 분산 (일반적으로 컬렉션) 지원.

일반 유형의 경우 마커 인터페이스에서 동일한 일반 매개 변수를 사용할 수 있습니다. 이것은 속성으로는 달성 할 수 없습니다.

interface MyInterface<T> {}

class MyClass<T, U> : MyInterface<U> {}

class OtherClass<T, U> : MyInterface<IDictionary<U, T>> {}

이러한 종류의 인터페이스는 유형을 다른 유형과 연관시키는 데 유용 할 수 있습니다.

마커 인터페이스의 또 다른 좋은 용도는 일종의 믹스 인 을 만들고 싶을 때 입니다 .

interface MyMixin {}

static class MyMixinMethods {
  public static void Method(this MyMixin self) {}
}

class MyClass : MyMixin {
}

비순환 방문자 패턴 도를 사용합니다. "degenerate interface"라는 용어도 때때로 사용됩니다.

최신 정보:

이것이 중요한지는 모르겠지만 포스트 컴파일러 가 작업 할 클래스를 표시하는 데 사용했습니다 .


Microsoft는 .NET 1.0을 만들 때 지침을 엄격하게 따르지 않았습니다. 지침은 프레임 워크와 함께 발전했기 때문이며 일부 규칙은 API를 변경하기에는 너무 늦을 때까지 배우지 않았습니다.

IIRC, 당신이 언급 한 예제는 BCL 1.0에 속하므로 그것을 설명 할 것입니다.

이것은 프레임 워크 디자인 지침에 설명되어 있습니다.


즉,이 책은 또한 "[A] ttribute 테스트는 유형 검사보다 훨씬 더 많은 비용이 듭니다"(Rico Mariani의 사이드 바에서)라고 언급합니다.

때때로 컴파일 시간 검사를 위해 마커 인터페이스가 필요하다고 말하는데, 이는 속성으로는 불가능합니다. 그러나 나는 책 (p. 88)에 제공된 예가 설득력이 없다고 생각하므로 여기서 반복하지 않겠습니다.


저는 강력하게 프로 마커 인터페이스입니다. 나는 속성을 좋아하지 않았습니다. 예를 들어 디버거가 살펴볼 클래스와 멤버에 대한 일종의 메타 정보라고 생각합니다. 예외와 유사하게, 저의 가장 겸손한 의견으로는 일반적인 처리 로직에 영향을주지 않아야합니다.


코딩 관점에서 볼 때 키워드 as.NET Framework가 내장되어 있기 때문에 Marker Interface 구문을 선호한다고 생각합니다 is. 속성 표시에는 약간 더 많은 코드가 필요합니다.

[MarkedByAttribute]
public class MarkedClass : IMarkByInterface
{
}

public class MarkedByAttributeAttribute : Attribute
{
}

public interface IMarkByInterface
{
}

public static class AttributeExtension
{
    public static bool HasAttibute<T>(this object obj)
    {
        var hasAttribute = Attribute.GetCustomAttribute(obj.GetType(), typeof(T));
        return hasAttribute != null;
    }
}

코드를 사용하는 몇 가지 테스트 :

using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class ClassMarkingTests
{
    private MarkedClass _markedClass;

    [TestInitialize]
    public void Init()
    {
        _markedClass = new MarkedClass();
    }

    [TestMethod]
    public void TestClassAttributeMarking()
    {
        var hasMarkerAttribute = _markedClass.HasAttibute<MarkedByAttributeAttribute>();
        Assert.IsTrue(hasMarkerAttribute);
    }

    [TestMethod]
    public void TestClassInterfaceMarking()
    {
        var hasMarkerInterface = _markedClass as IMarkByInterface;
        Assert.IsTrue(hasMarkerInterface != null);            
    }
} 

성능 관점에서 :

반사 때문에 마커 속성은 마커 인터페이스보다 느립니다. 리플렉션을 캐시하지 않으면 GetCustomAttributes항상 호출 하면 성능 병목 현상이 발생할 수 있습니다. 나는 이것을 전에 벤치마킹했고 마커 인터페이스를 사용하면 캐시 된 리플렉션을 사용할 때도 성능 측면에서 승리했습니다.

이것은 자주 호출되는 코드에서 사용할 때만 적용됩니다.

BenchmarkDotNet=v0.10.14, OS=Windows 10.0.16299.371 (1709/FallCreatorsUpdate/Redstone3)
Intel Core i5-2400 CPU 3.10GHz (Sandy Bridge), 1 CPU, 4 logical and 4 physical cores
Frequency=3020482 Hz, Resolution=331.0730 ns, Timer=TSC
.NET Core SDK=2.1.300-rc1-008673
  [Host] : .NET Core 2.0.7 (CoreCLR 4.6.26328.01, CoreFX 4.6.26403.03), 64bit RyuJIT
  Core   : .NET Core 2.0.7 (CoreCLR 4.6.26328.01, CoreFX 4.6.26403.03), 64bit RyuJIT

Job=Core  Runtime=Core

                     Method |          Mean |      Error |     StdDev | Rank |
--------------------------- |--------------:|-----------:|-----------:|-----:|
                     CastIs |     0.0000 ns |  0.0000 ns |  0.0000 ns |    1 |
                     CastAs |     0.0039 ns |  0.0059 ns |  0.0052 ns |    2 |
            CustomAttribute | 2,466.7302 ns | 18.5357 ns | 17.3383 ns |    4 |
 CustomAttributeWithCaching |    25.2832 ns |  0.5055 ns |  0.4729 ns |    3 |

그래도 큰 차이는 아닙니다.

namespace BenchmarkStuff
{
    [AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
    public class CustomAttribute : Attribute
    {

    }

    public interface ITest
    {

    }

    [Custom]
    public class Test : ITest
    {

    }

    [CoreJob]
    [RPlotExporter, RankColumn]
    public class CastVsCustomAttributes
    {
        private Test testObj;
        private Dictionary<Type, bool> hasCustomAttr;

        [GlobalSetup]
        public void Setup()
        {
            testObj = new Test();
            hasCustomAttr = new Dictionary<Type, bool>();
        }

        [Benchmark]
        public void CastIs()
        {
            if (testObj is ITest)
            {

            }
        }

        [Benchmark]
        public void CastAs()
        {
            var itest = testObj as ITest;
            if (itest != null)
            {

            }
        }

        [Benchmark]
        public void CustomAttribute()
        {
            var customAttribute = (CustomAttribute)testObj.GetType().GetCustomAttributes(typeof(CustomAttribute), false).SingleOrDefault();
            if (customAttribute != null)
            {

            }
        }

        [Benchmark]
        public void CustomAttributeWithCaching()
        {
            var type = testObj.GetType();
            bool hasAttr = false;
            if (!hasCustomAttr.TryGetValue(type, out hasAttr))
            {
                hasCustomAttr[type] = type.CustomAttributes.SingleOrDefault(attr => attr.AttributeType == typeof(CustomAttribute)) != null;
            }
            if (hasAttr)
            {

            }
        }
    }

    public static class Program
    {
        public static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<CastVsCustomAttributes>();
        }
    }
}

참조 URL : https://stackoverflow.com/questions/2086451/compelling-reasons-to-use-marker-interfaces-instead-of-attributes

반응형