인터페이스를 C # 제네릭 형식 제약 조건으로 사용하려면 어떻게해야합니까?


164

다음 함수 선언을 얻는 방법이 있습니까?

public bool Foo<T>() where T : interface;

즉. 여기서 T는 인터페이스 유형입니다 ( where T : class및 및 유사 struct).

현재 나는 정착했다 :

public bool Foo<T>() where T : IBase;

IBase가 모든 사용자 정의 인터페이스에서 상속되는 빈 인터페이스로 정의되는 경우 ... 이상적이지는 않지만 작동해야합니다. 일반 유형이 인터페이스 여야한다고 정의 할 수없는 이유는 무엇입니까?

가치 Foo가있는 것은 인터페이스 유형이 필요한 곳에서 리플렉션을 수행 하기 때문에 이것을 원합니다 ... 일반 매개 변수로 전달하고 함수 자체에서 필요한 검사를 수행 할 수는 있지만 훨씬 더 안전합니다. 모든 검사가 컴파일 타임에 수행되므로 약간 더 성능이 있다고 가정하십시오.


4
사실, 당신의 IBase dea는 내가 지금까지 본 것 중 최고입니다. 불행히도, 소유하지 않은 인터페이스에는 사용할 수 없습니다. 모든 C #은 모든 클래스가 Object에서 상속하는 것처럼 IOjbect에서 상속하는 것입니다.
Rhyous

1
참고 : 이것은 다소 일반적인 아이디어입니다. IBase이런 식으로 사용되는 빈 인터페이스를 마커 인터페이스 라고 합니다 . '표시된'유형에 대한 특수 동작을 가능하게합니다.
pius

답변:


132

당신이 할 수있는 가장 가까운 것은 (기본 인터페이스 접근법을 제외하고) where T : class참조 유형을 의미하는 " "입니다. "모든 인터페이스"를 의미하는 구문은 없습니다.

이 ( " where T : class")는 예를 들어 WCF에서 클라이언트가 서비스 계약 (인터페이스)으로 제한하는 데 사용됩니다.


7
좋은 대답이지만 왜이 구문이 존재하지 않는지 알고 있습니까? 좋은 기능인 것 같습니다.
Stephen Holt

@StephenHolt : .NET의 제작자는 허용 할 제약 조건을 결정할 때 일반 클래스와 메서드가 일반 유형과 메서드를 사용하지 못하게하는 것이 아니라 그렇지 않은 일반 유형의 작업을 수행 할 수있게 해주는 것에 중점을 두었다고 생각합니다. 무의미한 방법. 있었던 것 있음을 말했다 interface에 제약 T사이의 참조 비교를 허용해야 T참조 비교가 어떤 인터페이스와 거의 모든 다른 참조 타입 사이에 허용하고, 경우에 문제를 제기하지 않을 것이다조차 비교를 허용하고 있기 때문에, 그리고 다른 참조 유형.
supercat

1
@supercat 이러한 가상 제약의 또 다른 유용한 응용 프로그램은 유형의 인스턴스에 대한 프록시를 안전하게 만드는 것입니다. 인터페이스의 경우 안전하다는 것이 보장되지만 봉인 클래스의 경우 가상이 아닌 메소드가있는 클래스와 마찬가지로 실패합니다.
Ivan Danilov

@IvanDanilov : 허용 될 경우, 일부 무의미한 구성을 유용하게 차단할 수있는 많은 제약이 있습니다. "모든 인터페이스 유형"에 대한 제약 조건은 훌륭하지만 동의하지 않으면 수행 할 수없는 모든 것을 허용한다는 것을 알지 못합니다. 시도 할 때 컴파일 타임 스 쿼크 생성을 제외하고 그렇지 않으면 런타임에 실패 할 수있는 것들.
supercat

113

나는 이것이 조금 늦다는 것을 알고 있지만 관심있는 사람들은 런타임 검사를 사용할 수 있습니다.

typeof(T).IsInterface

11
이것을 지적하는 유일한 대답 인 +1. 방금 메소드가 호출 될 때마다 각 유형을 한 번만 확인하여 성능을 향상시키는 접근 방식으로 답변을 추가했습니다.
phoog

9
C #에서 제네릭의 전체 아이디어는 컴파일 타임 안전을 유지하는 것입니다. 당신이 제안하는 것은 제네릭이 아닌 방법으로도 수행 할 수 있습니다 Foo(Type type).
Jacek Gorgoń

나는 런타임 검사를 좋아한다. 감사.
Tarık Özgün Güner

또한 런타임 if (new T() is IMyInterface) { }에 인터페이스가 T 클래스에 의해 구현되는지 확인하는 데 사용할 수 있습니다 . 가장 효율적이지는 않지만 작동합니다.
tkerwood

26

사실, 당신이 생각 class하고 es와 s를 struct의미 한다면 , 당신은 잘못입니다. 수단 임의의 참조 유형 (예 인터페이스도 포함)과 의미 있는 값의 종류 (예를 들어 , ).classstructclassstructstructenum


1
그래도 클래스와 구조체의 차이점에 대한 정의는 아닙니다. 모든 클래스는 참조 유형이며 그 반대도 마찬가지입니다.
매튜 Scharley

Matthew : C # 구조체보다 유형이 더 가치가 있습니다. 예를 들어 열거 형은 값 형식과 일치 where T : struct제약 조건입니다.
Mehrdad Afshari

제약 조건에 사용되는 인터페이스 유형이 암시하지는 class않지만 인터페이스 유형의 저장 위치를 ​​선언하면 실제로 저장 위치가 해당 유형을 구현하는 클래스 참조로 선언됩니다.
supercat

4
더욱 정확한 수 where T : struct에 대응하는 NotNullableValueTypeConstraint, 그래서이 값 유형이어야 수단 다른 보다 Nullable<>. (그래서는 Nullable<>만족하지 않는 구조체의 유형입니다 where T : struct제약.)
Jeppe의 Stig 닐슨

19

Robert의 답변에 대한 후속 조치는 나중에 이루어 지지만 정적 도우미 클래스를 사용하여 유형별로 런타임 검사를 한 번만 수행 할 수 있습니다.

public bool Foo<T>() where T : class
{
    FooHelper<T>.Foo();
}

private static class FooHelper<TInterface> where TInterface : class
{
    static FooHelper()
    {
        if (!typeof(TInterface).IsInterface)
            throw // ... some exception
    }
    public static void Foo() { /*...*/ }
}

또한 "작동해야합니다"솔루션은 실제로 작동하지 않습니다. 치다:

public bool Foo<T>() where T : IBase;
public interface IBase { }
public interface IActual : IBase { string S { get; } }
public class Actual : IActual { public string S { get; set; } }

이제 Foo를 호출하는 것을 막을 수있는 것은 없습니다 :

Foo<Actual>();

결국 Actual클래스는 IBase제약 조건을 충족시킵니다 .


static생성자는 할 수 없다 public이 컴파일 타임 오류를 제공해야하므로. 또한 static클래스에는 인스턴스 메소드가 포함되어 있는데 컴파일 타임 오류이기도합니다.
Jeppe Stig Nielsen

@JeppeStigNielsen
phoog

10

한동안 나는 컴파일 시간에 가까운 제약에 대해 생각하고 있었으므로 이것은 개념을 시작할 수있는 완벽한 기회입니다.

기본적인 아이디어는 체크 컴파일 시간을 수행 할 수없는 경우 가장 빠른 시점에 수행해야한다는 것입니다. 기본적으로 응용 프로그램이 시작되는 순간입니다. 모든 검사가 정상이면 응용 프로그램이 실행됩니다. 확인에 실패하면 응용 프로그램이 즉시 실패합니다.

행동

최상의 결과는 제약 조건이 충족되지 않으면 프로그램이 컴파일되지 않는다는 것입니다. 불행히도 현재 C # 구현에서는 불가능합니다.

다음으로 가장 좋은 점은 프로그램이 시작되는 순간 충돌한다는 것입니다.

마지막 옵션은 코드를 누르는 순간 프로그램이 중단되는 것입니다. 이것이 .NET의 기본 동작입니다. 나에게 이것은 완전히 받아 들일 수 없다.

전제 조건

제약 메커니즘이 필요하므로 더 나은 것이 없으면 속성을 사용합시다. 속성이 일반 제약 조건 위에 존재하여 조건과 일치하는지 확인합니다. 그렇지 않으면 추악한 오류가 발생합니다.

이를 통해 코드에서 다음과 같은 작업을 수행 할 수 있습니다.

public class Clas<[IsInterface] T> where T : class

( where T:class런타임 검사보다 항상 컴파일 타임 검사를 선호하기 때문에 여기에 보관했습니다. )

따라서 우리에게 사용되는 모든 유형이 제약 조건과 일치하는지 확인하는 문제는 하나만 남습니다. 얼마나 힘들어요?

헤어지자

제네릭 형식은 항상 클래스 (/ struct / interface) 또는 메서드에 있습니다.

제약 조건을 트리거하려면 다음 중 하나를 수행해야합니다.

  1. 형식에서 형식을 사용할 때 컴파일 시간 (상속, 일반 제약 조건, 클래스 멤버)
  2. 메소드 본문에서 유형을 사용할 때 컴파일 타임
  3. 리플렉션을 사용하여 일반 기본 클래스를 기반으로 무언가를 구성 할 때의 런타임
  4. RTTI를 기반으로 무언가를 생성하기 위해 리플렉션을 사용할 때의 런타임.

이 시점에서 모든 프로그램 IMO에서 항상 (4)를 수행하지 말아야한다고 말하고 싶습니다. 어쨌든 이러한 검사는 중지 문제를 효과적으로 해결한다는 의미이므로 지원하지 않습니다.

사례 1 : 유형 사용

예:

public class TestClass : SomeClass<IMyInterface> { ... } 

예 2 :

public class TestClass 
{ 
    SomeClass<IMyInterface> myMember; // or a property, method, etc.
} 

기본적으로 여기에는 모든 유형, 상속, 멤버, 매개 변수 등을 스캔하는 것이 포함됩니다. 유형이 일반 유형이고 제약 조건이있는 경우 제약 조건을 확인합니다. 배열이면 요소 유형을 확인합니다.

이 시점에서 기본적으로 .NET이 'lazy'유형을로드한다는 사실을 깨뜨릴 것이라고 덧붙여 야합니다. 모든 유형을 스캔하여 .NET 런타임이 모든 유형을로드하도록합니다. 대부분의 프로그램에서 이것은 문제가되지 않습니다. 여전히 코드에서 정적 이니셜 라이저를 사용하면이 접근법에 문제가 발생할 수 있습니다 ... 즉, 어쨌든 누군가 에게이 작업을 수행하도록 조언하지는 않습니다 (이와 같은 것을 제외하고는 :-). 많은 문제가 있습니다.

사례 2 : 메소드에서 유형 사용

예:

void Test() {
    new SomeClass<ISomeInterface>();
}

이것을 확인하기 위해 우리는 단 하나의 옵션이 있습니다 : 클래스를 디 컴파일하고, 사용되는 모든 멤버 토큰을 확인하고 그중 하나가 일반 유형인지 확인하십시오-인수를 확인하십시오.

사례 3 : 리플렉션, 런타임 일반 구성

예:

typeof(CtorTest<>).MakeGenericType(typeof(IMyInterface))

이론적으로 사례 (2)와 비슷한 트릭으로 이것을 확인할 수 있지만 구현이 훨씬 어렵습니다 ( MakeGenericType일부 코드 경로에서 호출 되는지 확인해야 함). 여기서 자세히 설명하지 않겠습니다 ...

사례 4 : 리플렉션, 런타임 RTTI

예:

Type t = Type.GetType("CtorTest`1[IMyInterface]");

이것은 최악의 시나리오이며 일반적으로 나쁜 아이디어 IMHO 전에 설명했듯이. 어느 쪽이든, 수표를 사용하여 이것을 알아낼 수있는 실용적인 방법은 없습니다.

로트 테스트

사례 (1)과 (2)를 테스트하는 프로그램을 만들면 다음과 같은 결과가 나타납니다.

[AttributeUsage(AttributeTargets.GenericParameter)]
public class IsInterface : ConstraintAttribute
{
    public override bool Check(Type genericType)
    {
        return genericType.IsInterface;
    }

    public override string ToString()
    {
        return "Generic type is not an interface";
    }
}

public abstract class ConstraintAttribute : Attribute
{
    public ConstraintAttribute() {}

    public abstract bool Check(Type generic);
}

internal class BigEndianByteReader
{
    public BigEndianByteReader(byte[] data)
    {
        this.data = data;
        this.position = 0;
    }

    private byte[] data;
    private int position;

    public int Position
    {
        get { return position; }
    }

    public bool Eof
    {
        get { return position >= data.Length; }
    }

    public sbyte ReadSByte()
    {
        return (sbyte)data[position++];
    }

    public byte ReadByte()
    {
        return (byte)data[position++];
    }

    public int ReadInt16()
    {
        return ((data[position++] | (data[position++] << 8)));
    }

    public ushort ReadUInt16()
    {
        return (ushort)((data[position++] | (data[position++] << 8)));
    }

    public int ReadInt32()
    {
        return (((data[position++] | (data[position++] << 8)) | (data[position++] << 0x10)) | (data[position++] << 0x18));
    }

    public ulong ReadInt64()
    {
        return (ulong)(((data[position++] | (data[position++] << 8)) | (data[position++] << 0x10)) | (data[position++] << 0x18) | 
                        (data[position++] << 0x20) | (data[position++] << 0x28) | (data[position++] << 0x30) | (data[position++] << 0x38));
    }

    public double ReadDouble()
    {
        var result = BitConverter.ToDouble(data, position);
        position += 8;
        return result;
    }

    public float ReadSingle()
    {
        var result = BitConverter.ToSingle(data, position);
        position += 4;
        return result;
    }
}

internal class ILDecompiler
{
    static ILDecompiler()
    {
        // Initialize our cheat tables
        singleByteOpcodes = new OpCode[0x100];
        multiByteOpcodes = new OpCode[0x100];

        FieldInfo[] infoArray1 = typeof(OpCodes).GetFields();
        for (int num1 = 0; num1 < infoArray1.Length; num1++)
        {
            FieldInfo info1 = infoArray1[num1];
            if (info1.FieldType == typeof(OpCode))
            {
                OpCode code1 = (OpCode)info1.GetValue(null);
                ushort num2 = (ushort)code1.Value;
                if (num2 < 0x100)
                {
                    singleByteOpcodes[(int)num2] = code1;
                }
                else
                {
                    if ((num2 & 0xff00) != 0xfe00)
                    {
                        throw new Exception("Invalid opcode: " + num2.ToString());
                    }
                    multiByteOpcodes[num2 & 0xff] = code1;
                }
            }
        }
    }

    private ILDecompiler() { }

    private static OpCode[] singleByteOpcodes;
    private static OpCode[] multiByteOpcodes;

    public static IEnumerable<ILInstruction> Decompile(MethodBase mi, byte[] ildata)
    {
        Module module = mi.Module;

        BigEndianByteReader reader = new BigEndianByteReader(ildata);
        while (!reader.Eof)
        {
            OpCode code = OpCodes.Nop;

            int offset = reader.Position;
            ushort b = reader.ReadByte();
            if (b != 0xfe)
            {
                code = singleByteOpcodes[b];
            }
            else
            {
                b = reader.ReadByte();
                code = multiByteOpcodes[b];
                b |= (ushort)(0xfe00);
            }

            object operand = null;
            switch (code.OperandType)
            {
                case OperandType.InlineBrTarget:
                    operand = reader.ReadInt32() + reader.Position;
                    break;
                case OperandType.InlineField:
                    if (mi is ConstructorInfo)
                    {
                        operand = module.ResolveField(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), Type.EmptyTypes);
                    }
                    else
                    {
                        operand = module.ResolveField(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                    }
                    break;
                case OperandType.InlineI:
                    operand = reader.ReadInt32();
                    break;
                case OperandType.InlineI8:
                    operand = reader.ReadInt64();
                    break;
                case OperandType.InlineMethod:
                    try
                    {
                        if (mi is ConstructorInfo)
                        {
                            operand = module.ResolveMember(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), Type.EmptyTypes);
                        }
                        else
                        {
                            operand = module.ResolveMember(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                        }
                    }
                    catch
                    {
                        operand = null;
                    }
                    break;
                case OperandType.InlineNone:
                    break;
                case OperandType.InlineR:
                    operand = reader.ReadDouble();
                    break;
                case OperandType.InlineSig:
                    operand = module.ResolveSignature(reader.ReadInt32());
                    break;
                case OperandType.InlineString:
                    operand = module.ResolveString(reader.ReadInt32());
                    break;
                case OperandType.InlineSwitch:
                    int count = reader.ReadInt32();
                    int[] targetOffsets = new int[count];
                    for (int i = 0; i < count; ++i)
                    {
                        targetOffsets[i] = reader.ReadInt32();
                    }
                    int pos = reader.Position;
                    for (int i = 0; i < count; ++i)
                    {
                        targetOffsets[i] += pos;
                    }
                    operand = targetOffsets;
                    break;
                case OperandType.InlineTok:
                case OperandType.InlineType:
                    try
                    {
                        if (mi is ConstructorInfo)
                        {
                            operand = module.ResolveMember(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), Type.EmptyTypes);
                        }
                        else
                        {
                            operand = module.ResolveMember(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                        }
                    }
                    catch
                    {
                        operand = null;
                    }
                    break;
                case OperandType.InlineVar:
                    operand = reader.ReadUInt16();
                    break;
                case OperandType.ShortInlineBrTarget:
                    operand = reader.ReadSByte() + reader.Position;
                    break;
                case OperandType.ShortInlineI:
                    operand = reader.ReadSByte();
                    break;
                case OperandType.ShortInlineR:
                    operand = reader.ReadSingle();
                    break;
                case OperandType.ShortInlineVar:
                    operand = reader.ReadByte();
                    break;

                default:
                    throw new Exception("Unknown instruction operand; cannot continue. Operand type: " + code.OperandType);
            }

            yield return new ILInstruction(offset, code, operand);
        }
    }
}

public class ILInstruction
{
    public ILInstruction(int offset, OpCode code, object operand)
    {
        this.Offset = offset;
        this.Code = code;
        this.Operand = operand;
    }

    public int Offset { get; private set; }
    public OpCode Code { get; private set; }
    public object Operand { get; private set; }
}

public class IncorrectConstraintException : Exception
{
    public IncorrectConstraintException(string msg, params object[] arg) : base(string.Format(msg, arg)) { }
}

public class ConstraintFailedException : Exception
{
    public ConstraintFailedException(string msg) : base(msg) { }
    public ConstraintFailedException(string msg, params object[] arg) : base(string.Format(msg, arg)) { }
}

public class NCTChecks
{
    public NCTChecks(Type startpoint)
        : this(startpoint.Assembly)
    { }

    public NCTChecks(params Assembly[] ass)
    {
        foreach (var assembly in ass)
        {
            assemblies.Add(assembly);

            foreach (var type in assembly.GetTypes())
            {
                EnsureType(type);
            }
        }

        while (typesToCheck.Count > 0)
        {
            var t = typesToCheck.Pop();
            GatherTypesFrom(t);

            PerformRuntimeCheck(t);
        }
    }

    private HashSet<Assembly> assemblies = new HashSet<Assembly>();

    private Stack<Type> typesToCheck = new Stack<Type>();
    private HashSet<Type> typesKnown = new HashSet<Type>();

    private void EnsureType(Type t)
    {
        // Don't check for assembly here; we can pass f.ex. System.Lazy<Our.T<MyClass>>
        if (t != null && !t.IsGenericTypeDefinition && typesKnown.Add(t))
        {
            typesToCheck.Push(t);

            if (t.IsGenericType)
            {
                foreach (var par in t.GetGenericArguments())
                {
                    EnsureType(par);
                }
            }

            if (t.IsArray)
            {
                EnsureType(t.GetElementType());
            }
        }

    }

    private void PerformRuntimeCheck(Type t)
    {
        if (t.IsGenericType && !t.IsGenericTypeDefinition)
        {
            // Only check the assemblies we explicitly asked for:
            if (this.assemblies.Contains(t.Assembly))
            {
                // Gather the generics data:
                var def = t.GetGenericTypeDefinition();
                var par = def.GetGenericArguments();
                var args = t.GetGenericArguments();

                // Perform checks:
                for (int i = 0; i < args.Length; ++i)
                {
                    foreach (var check in par[i].GetCustomAttributes(typeof(ConstraintAttribute), true).Cast<ConstraintAttribute>())
                    {
                        if (!check.Check(args[i]))
                        {
                            string error = "Runtime type check failed for type " + t.ToString() + ": " + check.ToString();

                            Debugger.Break();
                            throw new ConstraintFailedException(error);
                        }
                    }
                }
            }
        }
    }

    // Phase 1: all types that are referenced in some way
    private void GatherTypesFrom(Type t)
    {
        EnsureType(t.BaseType);

        foreach (var intf in t.GetInterfaces())
        {
            EnsureType(intf);
        }

        foreach (var nested in t.GetNestedTypes())
        {
            EnsureType(nested);
        }

        var all = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
        foreach (var field in t.GetFields(all))
        {
            EnsureType(field.FieldType);
        }
        foreach (var property in t.GetProperties(all))
        {
            EnsureType(property.PropertyType);
        }
        foreach (var evt in t.GetEvents(all))
        {
            EnsureType(evt.EventHandlerType);
        }
        foreach (var ctor in t.GetConstructors(all))
        {
            foreach (var par in ctor.GetParameters())
            {
                EnsureType(par.ParameterType);
            }

            // Phase 2: all types that are used in a body
            GatherTypesFrom(ctor);
        }
        foreach (var method in t.GetMethods(all))
        {
            if (method.ReturnType != typeof(void))
            {
                EnsureType(method.ReturnType);
            }

            foreach (var par in method.GetParameters())
            {
                EnsureType(par.ParameterType);
            }

            // Phase 2: all types that are used in a body
            GatherTypesFrom(method);
        }
    }

    private void GatherTypesFrom(MethodBase method)
    {
        if (this.assemblies.Contains(method.DeclaringType.Assembly)) // only consider methods we've build ourselves
        {
            MethodBody methodBody = method.GetMethodBody();
            if (methodBody != null)
            {
                // Handle local variables
                foreach (var local in methodBody.LocalVariables)
                {
                    EnsureType(local.LocalType);
                }

                // Handle method body
                var il = methodBody.GetILAsByteArray();
                if (il != null)
                {
                    foreach (var oper in ILDecompiler.Decompile(method, il))
                    {
                        if (oper.Operand is MemberInfo)
                        {
                            foreach (var type in HandleMember((MemberInfo)oper.Operand))
                            {
                                EnsureType(type);
                            }

                        }
                    }
                }
            }
        }
    }

    private static IEnumerable<Type> HandleMember(MemberInfo info)
    {
        // Event, Field, Method, Constructor or Property.
        yield return info.DeclaringType;
        if (info is EventInfo)
        {
            yield return ((EventInfo)info).EventHandlerType;
        }
        else if (info is FieldInfo)
        {
            yield return ((FieldInfo)info).FieldType;
        }
        else if (info is PropertyInfo)
        {
            yield return ((PropertyInfo)info).PropertyType;
        }
        else if (info is ConstructorInfo)
        {
            foreach (var par in ((ConstructorInfo)info).GetParameters())
            {
                yield return par.ParameterType;
            }
        }
        else if (info is MethodInfo)
        {
            foreach (var par in ((MethodInfo)info).GetParameters())
            {
                yield return par.ParameterType;
            }
        }
        else if (info is Type)
        {
            yield return (Type)info;
        }
        else
        {
            throw new NotSupportedException("Incorrect unsupported member type: " + info.GetType().Name);
        }
    }
}

코드 사용

글쎄, 그것은 쉬운 부분입니다 :-)

// Create something illegal
public class Bar2 : IMyInterface
{
    public void Execute()
    {
        throw new NotImplementedException();
    }
}

// Our fancy check
public class Foo<[IsInterface] T>
{
}

class Program
{
    static Program()
    {
        // Perform all runtime checks
        new NCTChecks(typeof(Program));
    }

    static void Main(string[] args)
    {
        // Normal operation
        Console.WriteLine("Foo");
        Console.ReadLine();
    }
}

8

출시 된 C # 버전이나 향후 C # 4.0에서는이 작업을 수행 할 수 없습니다. C # 제한도 아닙니다. CLR 자체에는 "인터페이스"제약 조건이 없습니다.


6

가능하다면 이와 같은 해결책을 찾았습니다. 여러 특정 인터페이스 (예 : 소스 액세스 권한이있는 인터페이스)를 일반 매개 변수로 전달하지 않고 원하는 경우에만 작동합니다.

  • 문제가 된 인터페이스가 빈 인터페이스를 상속하도록했습니다 IInterface.
  • 일반 T 매개 변수를 IInterface

소스에서는 다음과 같습니다.

  • 일반 매개 변수로 전달하려는 인터페이스 :

    public interface IWhatever : IInterface
    {
        // IWhatever specific declarations
    }
  • 인터페이스 :

    public interface IInterface
    {
        // Nothing in here, keep moving
    }
  • 타입 제약 조건을 넣을 클래스 :

    public class WorldPeaceGenerator<T> where T : IInterface
    {
        // Actual world peace generating code
    }

이것은 많은 것을 성취하지 못합니다. 귀하 T는 인터페이스에 제한되지 않고 구현 IInterface하는 모든 것에 제한됩니다 . 예를 들어 struct Foo : IInterface귀하 IInterface가 가장 공개적 일 가능성이 있기 때문에 (그렇지 않으면 내부적으로 받아 들여 져야 할 것이므로) 모든 유형이 원하는 경우 수행 할 수 있습니다 .
AnorZaken

어쨌든 수락하려는 모든 유형을 제어하는 ​​경우 코드 생성을 사용하여 적합한 오버로드를 모두 만들 수 있습니다.이 모든 오버로드는 일반적인 개인 메서드로 리디렉션됩니다.
AnorZaken

2

당신이 정착 한 것은 당신이 할 수있는 최선입니다 :

public bool Foo<T>() where T : IBase;

2

비슷한 것을 시도하고 해결 방법을 사용했습니다. 구조에 대한 암시 적 및 명시 적 연산자에 대해 생각했습니다. 아이디어는 암시 적으로 Type으로 변환 할 수있는 구조로 Type을 래핑하는 것입니다.

이러한 구조는 다음과 같습니다.

공용 구조체 InterfaceType {private Type _type;

public InterfaceType(Type type)
{
    CheckType(type);
    _type = type;
}

public static explicit operator Type(InterfaceType value)
{
    return value._type;
}

public static implicit operator InterfaceType(Type type)
{
    return new InterfaceType(type);
}

private static void CheckType(Type type)
{
    if (type == null) throw new NullReferenceException("The type cannot be null");
    if (!type.IsInterface) throw new NotSupportedException(string.Format("The given type {0} is not an interface, thus is not supported", type.Name));
}

}

기본 사용법 :

// OK
InterfaceType type1 = typeof(System.ComponentModel.INotifyPropertyChanged);

// Throws an exception
InterfaceType type2 = typeof(WeakReference);

이것에 대한 자신의 메카니즘을 상상해야하지만 예는 유형 대신 매개 변수에서 InterfaceType을 취하는 메소드가 될 수 있습니다

this.MyMethod(typeof(IMyType)) // works
this.MyMethod(typeof(MyType)) // throws exception

이를 재정의하는 메소드는 인터페이스 유형을 리턴해야합니다.

public virtual IEnumerable<InterfaceType> GetInterfaces()

제네릭과 관련이있을 수도 있지만 시도하지 않았습니다.

이것이 도움이되거나 아이디어를 줄 수 있기를 바랍니다 :-)


0

솔루션 A :이 제약 조건 조합은 TInterface인터페이스 임을 보증해야합니다 .

class example<TInterface, TStruct>
    where TStruct : struct, TInterface
    where TInterface : class
{ }

그것을 TStruct증명하는 증인으로서 하나의 구조체가 필요합니다 TInterface.

제네릭이 아닌 모든 유형의 증인으로 단일 구조체를 사용할 수 있습니다.

struct InterfaceWitness : IA, IB, IC 
{
    public int DoA() => throw new InvalidOperationException();
    //...
}

솔루션 B : 목격자로서 구조체를 만들고 싶지 않다면 인터페이스를 만들 수 있습니다

interface ISInterface<T>
    where T : ISInterface<T>
{ }

제약 조건을 사용하십시오.

class example<TInterface>
    where TInterface : ISInterface<TInterface>
{ }

인터페이스 구현 :

interface IA :ISInterface<IA>{ }

이것은 일부 문제를 해결하지만, ISInterface<T>비 인터페이스 유형에 대해 아무도 구현하지는 않지만 신뢰 하기 어려운 신뢰가 필요합니다 .


-4

대신 추상 클래스를 사용하십시오. 따라서 다음과 같은 것이 있습니다.

public bool Foo<T>() where T : CBase;

10
C #은 다중 상속을 지원하지 않기 때문에 항상 인터페이스를 추상 클래스로 바꿀 수는 없습니다.
Sam
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.