Como posso usar a interface como uma restrição de tipo genérico em C #?

164

Existe uma maneira de obter a seguinte declaração de função?

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

ie onde T é um tipo de interface (semelhante a where T : class, e struct).

Atualmente, eu me conformei com:

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

Onde o IBase é definido como uma interface vazia que é herdada por todas as minhas interfaces personalizadas ... Não é o ideal, mas deve funcionar ... Por que você não pode definir que um tipo genérico deve ser uma interface?

Pelo que vale, eu quero isso porque Fooestá refletindo onde precisa de um tipo de interface ... Eu poderia passar como um parâmetro normal e fazer a verificação necessária na própria função, mas isso parecia muito mais seguro (e eu suponha um pouco mais de desempenho, uma vez que todas as verificações são feitas em tempo de compilação).

Matthew Scharley
fonte
4
Na verdade, o seu IBase dea é o melhor que eu já vi até agora. Infelizmente, você não pode usá-lo para interfaces que não possui. Tudo que o C # precisaria fazer é herdar todas as interfaces do IOjbect, assim como todas as classes herdadas do Object.
Rhyous 4/15
1
Nota: Essa é uma ideia bastante comum. Interfaces vazias como IBase- usadas dessa maneira - são chamadas de interfaces de marcador . Eles permitem comportamentos especiais para tipos 'marcados'.
pius

Respostas:

132

O mais próximo que você pode fazer (exceto a abordagem da interface base) é " where T : class", significando o tipo de referência. Não há sintaxe para significar "qualquer interface".

Isso (" where T : class") é usado, por exemplo, no WCF para limitar os clientes a contratos de serviço (interfaces).

Marc Gravell
fonte
7
boa resposta, mas você tem alguma idéia de por que essa sintaxe não existe? Parece que seria um recurso interessante.
Stephen Holt
@StephenHolt: Eu acho que os criadores do .NET, ao decidir quais restrições permitir, estavam focados naqueles que permitiriam que classes e métodos genéricos fizessem coisas com tipos genéricos que eles não poderiam, em vez de impedir que eles fossem usados ​​em maneiras absurdas. Dito isto, uma interfacerestrição Tdeve permitir comparações de referência entre Te qualquer outro tipo de referência, uma vez que são permitidas comparações de referência entre qualquer interface e quase qualquer outro tipo de referência, e permitir comparações mesmo nesse caso não apresentaria problemas.
Supercat
1
@supercat outra aplicação útil dessa restrição hipotética seria criar com segurança um proxy para instâncias do tipo. Para a interface, é garantido que seja seguro, enquanto para as classes seladas falhará, o mesmo que para as classes com métodos não virtuais.
precisa
@IvanDanilov: Há uma série de restrições concebíveis que, se permitidas, bloqueariam utilmente algumas construções sem sentido. Concordo que uma restrição para "qualquer tipo de interface" seria legal, mas não vejo que isso permita algo que não possa ser feito sem ela, exceto pela geração de gritos em tempo de compilação quando são feitas tentativas coisas que poderiam falhar em tempo de execução.
Supercat
113

Eu sei que isso é um pouco tarde, mas para aqueles que estão interessados, você pode usar uma verificação de tempo de execução.

typeof(T).IsInterface
Robert
fonte
11
+1 por ser a única resposta para apontar isso. Acabei de adicionar uma resposta com uma abordagem para melhorar o desempenho, verificando cada tipo apenas uma vez e não toda vez que o método é chamado.
Phoog
9
Toda a idéia de genéricos em C # é ter segurança em tempo de compilação. O que você está sugerindo também pode ser executado com um método não genérico Foo(Type type).
Jacek Gorgoń
Eu gosto da verificação do tempo de execução. Obrigado.
Tarık Özgün Güner 19/03/2015
Também em tempo de execução, você pode usar if (new T() is IMyInterface) { }para verificar se uma interface é implementada pela classe T. Pode não ser o mais eficiente, mas funciona.
Tkerwood
26

Não, na verdade, se você está pensando classe structquer dizer classes e structs, está errado. classsignifica qualquer tipo de referência (por exemplo, também inclui interfaces) e structsignifica qualquer tipo de valor (por exemplo struct, enum).

Mehrdad Afshari
fonte
1
Não é que a definição da diferença entre uma classe e uma estrutura que: cada classe que é um tipo de referência (e vice-versa) e idem para tipos stuct / valor
Matthew Scharley
Matthew: Existem mais tipos de valor do que estruturas C #. Enums, por exemplo, são tipos de valor e correspondem à where T : structrestrição.
Mehrdad Afshari 08/07/09
Vale notar que os tipos de interface usados ​​nas restrições não implicam class, mas declarar um local de armazenamento de um tipo de interface realmente declara que o local de armazenamento é uma referência de classe que implementa esse tipo.
Supercat 17/05
4
Para ser ainda mais preciso, where T : structcorresponde a NotNullableValueTypeConstraint, portanto significa que deve ser um tipo de valor diferente de Nullable<>. (Então Nullable<>é um tipo struct que não satisfaz a where T : structrestrição.)
Jeppe Stig Nielsen
19

Para acompanhar a resposta de Robert, isso é ainda mais tarde, mas você pode usar uma classe auxiliar estática para fazer a verificação do tempo de execução apenas uma vez por tipo:

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() { /*...*/ }
}

Também observo que sua solução "deve funcionar" não funciona. Considerar:

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

Agora não há nada que o impeça de chamar Foo assim:

Foo<Actual>();

A Actualclasse, afinal, satisfaz a IBaserestrição.

phoog
fonte
Um staticconstrutor não pode ser public, portanto, isso deve gerar um erro em tempo de compilação. Além disso, sua staticclasse contém um método de instância, que também é um erro em tempo de compilação.
Jeppe Stig Nielsen
Agradecido pela nawfal por corrigir os erros observados por @JeppeStigNielsen
phoog 5/15
10

Já faz algum tempo que estou pensando em restrições de tempo de compilação, portanto esta é uma oportunidade perfeita para lançar o conceito.

A idéia básica é que, se você não puder verificar o tempo de compilação, faça-o o mais cedo possível, que é basicamente o momento em que o aplicativo é iniciado. Se todas as verificações estiverem corretas, o aplicativo será executado; se uma verificação falhar, o aplicativo falhará instantaneamente.

Comportamento

O melhor resultado possível é que nosso programa não seja compilado se as restrições não forem atendidas. Infelizmente, isso não é possível na implementação atual do C #.

A melhor coisa a seguir é que o programa falha no momento em que é iniciado.

A última opção é que o programa falhe no momento em que o código for atingido. Esse é o comportamento padrão do .NET. Para mim, isso é completamente inaceitável.

Pré requisitos

Precisamos ter um mecanismo de restrição, portanto, pela falta de algo melhor ... vamos usar um atributo. O atributo estará presente em cima de uma restrição genérica para verificar se ele corresponde às nossas condições. Caso contrário, damos um erro feio.

Isso nos permite fazer coisas assim em nosso código:

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

(Eu mantive o where T:classaqui, porque eu sempre prefiro verificações em tempo de compilação a verificações em tempo de execução)

Portanto, isso nos deixa com apenas 1 problema, que é verificar se todos os tipos que usamos correspondem à restrição. Quão difícil isso pode ser?

Vamos terminar

Os tipos genéricos estão sempre em uma classe (/ struct / interface) ou em um método.

O acionamento de uma restrição requer que você execute um dos seguintes procedimentos:

  1. Tempo de compilação, ao usar um tipo em um tipo (herança, restrição genérica, membro da classe)
  2. Tempo de compilação, ao usar um tipo em um corpo de método
  3. Tempo de execução, ao usar a reflexão para construir algo baseado na classe base genérica.
  4. Tempo de execução, ao usar a reflexão para construir algo baseado no RTTI.

Neste ponto, gostaria de afirmar que você deve sempre evitar fazer (4) em qualquer programa IMO. Independentemente disso, essas verificações não serão compatíveis, pois significariam efetivamente resolver o problema da interrupção.

Caso 1: usando um tipo

Exemplo:

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

Exemplo 2:

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

Basicamente, isso envolve a varredura de todos os tipos, herança, membros, parâmetros, etc, etc, etc. Se um tipo é um tipo genérico e tem uma restrição, verificamos a restrição; se for uma matriz, verificamos o tipo de elemento.

Neste ponto, devo acrescentar que isso quebrará o fato de que, por padrão, o .NET carrega os tipos 'preguiçoso'. Ao verificar todos os tipos, forçamos o tempo de execução do .NET a carregar todos eles. Para a maioria dos programas, isso não deve ser um problema; ainda assim, se você usar inicializadores estáticos no seu código, poderá encontrar problemas com essa abordagem ... Dito isso, eu não aconselharia ninguém a fazer isso de qualquer maneira (exceto para coisas como esta :-), por isso não deve dar você muitos problemas.

Caso 2: usando um tipo em um método

Exemplo:

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

Para verificar isso, temos apenas 1 opção: descompilar a classe, verificar todos os tokens de membros que são usados ​​e se um deles for do tipo genérico - verifique os argumentos.

Caso 3: Reflexão, construção genérica de tempo de execução

Exemplo:

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

Suponho que seja teoricamente possível verificar isso com truques semelhantes ao caso (2), mas a implementação é muito mais difícil (você precisa verificar se MakeGenericTypeé chamado em algum caminho de código). Não vou entrar em detalhes aqui ...

Caso 4: Reflexão, RTTI em tempo de execução

Exemplo:

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

Este é o pior cenário possível e, como expliquei antes, geralmente é uma péssima ideia IMHO. De qualquer maneira, não há uma maneira prática de descobrir isso usando cheques.

Testando o lote

Criar um programa que teste os casos (1) e (2) resultará em algo como isto:

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

Usando o código

Bem, essa é a parte mais fácil :-)

// 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();
    }
}
atlaste
fonte
8

Você não pode fazer isso em nenhuma versão lançada do C #, nem no próximo C # 4.0. Também não é uma limitação de C # - não há restrição de "interface" no próprio CLR.

Pavel Minaev
fonte
6

Se possível, eu fui com uma solução como esta. Funciona apenas se você deseja que várias interfaces específicas (por exemplo, aquelas às quais você tem acesso de origem) sejam passadas como um parâmetro genérico, e não qualquer.

  • Deixei minhas interfaces, que foram questionadas, herdarem uma interface vazia IInterface.
  • Eu restringi o parâmetro T genérico para ser de IInterface

Na fonte, fica assim:

  • Qualquer interface que você deseja que seja transmitida como parâmetro genérico:

    public interface IWhatever : IInterface
    {
        // IWhatever specific declarations
    }
  • IInterface:

    public interface IInterface
    {
        // Nothing in here, keep moving
    }
  • A classe na qual você deseja colocar a restrição de tipo:

    public class WorldPeaceGenerator<T> where T : IInterface
    {
        // Actual world peace generating code
    }
Raio
fonte
Isso não faz muito. Você Tnão está restrito a interfaces, está restrito a qualquer coisa que implemente IInterface- o que qualquer tipo pode fazer se quiser, por exemplo, struct Foo : IInterfaceuma vez que você IInterfaceé provavelmente público (caso contrário, tudo o que aceita deve ser interno).
AnorZaken 25/02
Se você controlar todos os tipos que deseja aceitar de qualquer maneira, poderá usar a geração de código para criar todas as sobrecargas adequadas, todas as quais serão redirecionadas apenas para um método privado genérico.
AnorZaken 25/02
2

O que você decidiu é o melhor que pode fazer:

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

Tentei fazer algo semelhante e usei uma solução alternativa: pensei no operador implícito e explícito na estrutura: a idéia é envolver o Type em uma estrutura que possa ser convertida em Type implicitamente.

Aqui está uma estrutura:

estrutura pública 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));
}

}

uso básico:

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

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

Você deve imaginar seu próprio mecanismo em torno disso, mas um exemplo pode ser um método usado como um InterfaceType no parâmetro, em vez de um tipo

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

Um método para substituir que deve retornar os tipos de interface:

public virtual IEnumerable<InterfaceType> GetInterfaces()

Talvez haja coisas a ver com genéricos também, mas eu não tentei

Espero que isso possa ajudar ou dê idéias :-)

Charles HETIER
fonte
0

Solução A: Essa combinação de restrições deve garantir que TInterfaceé uma interface:

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

Requer uma estrutura única TStructcomo testemunha para provar que TInterfaceé uma estrutura.

Você pode usar uma única estrutura como testemunha para todos os seus tipos não genéricos:

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

Solução B: Se você não deseja criar estruturas como testemunhas, pode criar uma interface

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

e use uma restrição:

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

Implementação para interfaces:

interface IA :ISInterface<IA>{ }

Isso resolve alguns dos problemas, mas requer confiança que ninguém implementa ISInterface<T>para tipos que não são de interface, mas isso é bastante difícil de fazer acidentalmente.

user13909106
fonte
-4

Use uma classe abstrata em seu lugar. Então, você teria algo como:

public bool Foo<T>() where T : CBase;
Eddie
fonte
10
Você nem sempre pode substituir uma interface por uma classe abstrata, pois o C # não suporta herança múltipla.
Sam