인터페이스를 C # 제네릭 형식 제약 조건으로 사용하려면 어떻게해야합니까? … 이상적이지는 않지만 작동해야합니다. 일반 유형이

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

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

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

현재 나는 정착했다 :

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

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

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



답변

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

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


답변

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

typeof(T).IsInterface

답변

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


답변

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제약 조건을 충족시킵니다 .


답변

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

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

행동

최상의 결과는 제약 조건이 충족되지 않으면 프로그램이 컴파일되지 않는다는 것입니다. 불행히도 현재 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();
    }
}

답변

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


답변

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

  • 문제가 된 인터페이스가 빈 인터페이스를 상속하도록했습니다 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
    }