¿Cómo puedo usar la interfaz como una restricción de tipo genérico C #?


164

¿Hay alguna manera de obtener la siguiente declaración de función?

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

es decir. donde T es un tipo de interfaz (similar a where T : classy struct).

Actualmente me he conformado con:

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

Donde IBase se define como una interfaz vacía que es heredada por todas mis interfaces personalizadas ... No es ideal, pero debería funcionar ... ¿Por qué no puede definir que un tipo genérico debe ser una interfaz?

Para lo que vale, quiero esto porque Fooestá haciendo una reflexión donde necesita un tipo de interfaz ... Podría pasarlo como un parámetro normal y hacer la verificación necesaria en la función en sí, pero esto parecía mucho más seguro (y yo supongamos un poco más de rendimiento, ya que todas las comprobaciones se realizan en tiempo de compilación).


44
En realidad, tu IBase dea es el mejor que he visto hasta ahora. Desafortunadamente, no puede usarlo para interfaces que no son de su propiedad. Todo lo que C # tendría que hacer es que todas las interfaces hereden de IOjbect al igual que todas las clases heredan de Object.
Rhyous

1
Nota: Esta es una idea bastante común. Las interfaces vacías como IBase, utilizadas de esta manera, se denominan interfaces de marcador . Permiten comportamientos especiales para tipos 'marcados'.
pío

Respuestas:


132

Lo más cercano que puede hacer (excepto su enfoque de interfaz base) es " where T : class", que significa tipo de referencia. No hay sintaxis que signifique "cualquier interfaz".

Este (" where T : class") se usa, por ejemplo, en WCF para limitar clientes a contratos de servicio (interfaces).


77
buena respuesta, pero ¿tienes alguna idea de por qué esta sintaxis no existe? Parece que sería una característica agradable de tener.
Stephen Holt

@StephenHolt: Creo que los creadores de .NET, al decidir qué restricciones permitir, se centraron en aquellas que permitirían que las clases y métodos genéricos hicieran cosas con tipos genéricos que de otro modo no podrían, en lugar de evitar que se usen en formas sin sentido. Dicho esto, una interfacerestricción Tdebería permitir las comparaciones de referencia entre Ty cualquier otro tipo de referencia, ya que las comparaciones de referencia están permitidas entre cualquier interfaz y casi cualquier otro tipo de referencia, y permitir comparaciones incluso en ese caso no plantearía ningún problema.
supercat

1
@supercat otra aplicación útil de dicha restricción hipotética sería crear de manera segura un proxy para instancias del tipo. Para la interfaz se garantiza que es seguro, mientras que para las clases selladas fallaría, al igual que para las clases con métodos no virtuales.
Ivan Danilov

@IvanDanilov: hay una serie de restricciones concebibles que, si se permiten, bloquearían útilmente algunas construcciones sin sentido. Estoy de acuerdo en que una restricción para "cualquier tipo de interfaz" sería buena, pero no veo que permita nada que no se pueda hacer sin ella, salvo la generación de graznidos en tiempo de compilación cuando se hacen intentos cosas que de otro modo podrían fallar en tiempo de ejecución.
supercat

113

Sé que esto es un poco tarde, pero para aquellos que estén interesados, pueden usar una verificación de tiempo de ejecución.

typeof(T).IsInterface

11
+1 por ser la única respuesta para señalar esto. Acabo de agregar una respuesta con un enfoque para mejorar el rendimiento al verificar cada tipo solo una vez en lugar de cada vez que se llama al método.
phoog

9
La idea general de los genéricos en C # es tener seguridad en tiempo de compilación. Lo que estás sugiriendo también se puede realizar con un método no genérico Foo(Type type).
Jacek Gorgoń

Me gusta el cheque de tiempo de ejecución. Gracias.
Tarık Özgün Güner

También en tiempo de ejecución puede usar if (new T() is IMyInterface) { }para verificar si la clase T implementa una interfaz. Puede que no sea el más eficiente, pero funciona.
tkerwood

26

No, en realidad, si estás pensando classy quieres structdecir classes y structs, estás equivocado. classsignifica cualquier tipo de referencia (por ejemplo, también incluye interfaces) y structsignifica cualquier tipo de valor (por ejemplo struct, enum).


1
¿No es la definición de la diferencia entre una clase y una estructura sin embargo: que cada clase es un tipo de referencia (y viceversa) y lo mismo para los tipos stuct / valor
Mateo Scharley

Matthew: Hay más para valorar tipos que estructuras de C #. Las enumeraciones, por ejemplo, son tipos de valores y where T : structrestricciones de coincidencia .
Mehrdad Afshari

Vale la pena señalar que los tipos de interfaz utilizados en las restricciones no implican class, pero declarar una ubicación de almacenamiento de un tipo de interfaz realmente declara que la ubicación de almacenamiento es una referencia de clase que implementa ese tipo.
supercat

44
Para ser aún más preciso, where T : structcorresponde a NotNullableValueTypeConstraint, por lo que significa que debe ser un tipo de valor distinto de Nullable<>. (Entonces, Nullable<>es un tipo de estructura que no satisface la where T : structrestricción.)
Jeppe Stig Nielsen

19

Para seguir la respuesta de Robert, esto es aún más tarde, pero puede usar una clase auxiliar estática para hacer que el tiempo de ejecución se verifique solo una 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() { /*...*/ }
}

También noto que su solución "debería funcionar" no funciona, de hecho. 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; } }

Ahora no hay nada que te impida llamar a Foo así:

Foo<Actual>();

La Actualclase, después de todo, satisface la IBaserestricción.


Un staticconstructor no puede ser public, así que esto debería dar un error en tiempo de compilación. Además, su staticclase contiene un método de instancia, que también es un error en tiempo de compilación.
Jeppe Stig Nielsen

Agradecido a nawfal por corregir los errores observados por @JeppeStigNielsen
phoog el

10

Hace tiempo que pienso en las limitaciones de tiempo de compilación, por lo que esta es una oportunidad perfecta para lanzar el concepto.

La idea básica es que si no puede hacer una verificación del tiempo de compilación, debe hacerlo lo antes posible, que es básicamente el momento en que se inicia la aplicación. Si todas las comprobaciones están bien, la aplicación se ejecutará; Si falla un cheque, la aplicación fallará instantáneamente.

Comportamiento

El mejor resultado posible es que nuestro programa no compila si no se cumplen las restricciones. Desafortunadamente, eso no es posible en la implementación actual de C #.

La siguiente mejor opción es que el programa se bloquea en el momento en que se inicia.

La última opción es que el programa se bloqueará en el momento en que se presione el código. Este es el comportamiento predeterminado de .NET. Para mí, esto es completamente inaceptable.

Pre requisitos

Necesitamos tener un mecanismo de restricción, así que por falta de algo mejor ... usemos un atributo. El atributo estará presente sobre una restricción genérica para verificar si cumple con nuestras condiciones. Si no es así, damos un error feo.

Esto nos permite hacer cosas como esta en nuestro código:

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

(He guardado el where T:classaquí, porque siempre prefiero las comprobaciones en tiempo de compilación a las comprobaciones en tiempo de ejecución)

Entonces, eso solo nos deja con 1 problema, que es verificar si todos los tipos que usamos coinciden con la restricción. ¿Qué tan difícil puede ser?

Vamos a romperlo

Los tipos genéricos siempre están en una clase (/ struct / interface) o en un método.

Activar una restricción requiere que realice una de las siguientes cosas:

  1. Tiempo de compilación, cuando se usa un tipo en un tipo (herencia, restricción genérica, miembro de la clase)
  2. Tiempo de compilación, cuando se usa un tipo en un cuerpo de método
  3. Tiempo de ejecución, cuando se usa la reflexión para construir algo basado en la clase base genérica.
  4. Tiempo de ejecución, cuando se usa la reflexión para construir algo basado en RTTI.

En este punto, me gustaría decir que siempre debe evitar hacer (4) en cualquier programa IMO. De todos modos, estas comprobaciones no lo admitirán, ya que significaría efectivamente resolver el problema de detención.

Caso 1: usando un tipo

Ejemplo:

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

Ejemplo 2

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

Básicamente, esto implica escanear todos los tipos, herencia, miembros, parámetros, etc., etc., etc. Si un tipo es genérico y tiene una restricción, verificamos la restricción; si es una matriz, verificamos el tipo de elemento.

En este punto, debo agregar que esto romperá el hecho de que, por defecto, .NET carga los tipos 'perezosos'. Al escanear todos los tipos, forzamos el tiempo de ejecución de .NET para cargarlos a todos. Para la mayoría de los programas esto no debería ser un problema; aún así, si usa inicializadores estáticos en su código, puede encontrar problemas con este enfoque ... Dicho esto, no recomendaría a nadie que haga esto de todos modos (excepto por cosas como esta :-), por lo que no debería dar Tienes muchos problemas.

Caso 2: uso de un tipo en un método

Ejemplo:

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

Para verificar esto, solo tenemos 1 opción: descompilar la clase, verificar todos los tokens de miembros que se usan y si uno de ellos es del tipo genérico, verifique los argumentos.

Caso 3: Reflexión, construcción genérica en tiempo de ejecución

Ejemplo:

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

Supongo que es teóricamente posible verificar esto con trucos similares al caso (2), pero la implementación es mucho más difícil (debe verificar si MakeGenericTypese llama en alguna ruta de código). No entraré en detalles aquí ...

Caso 4: Reflexión, tiempo de ejecución RTTI

Ejemplo:

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

Este es el peor de los casos y, como expliqué antes, generalmente es una mala idea en mi humilde opinión. De cualquier manera, no hay una forma práctica de resolver esto usando cheques.

Probar el lote

Crear un programa que pruebe los casos (1) y (2) resultará en algo como esto:

[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 el código

Bueno, esa es la parte 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();
    }
}

8

No puede hacer esto en ninguna versión lanzada de C #, ni en la próxima C # 4.0. Tampoco es una limitación de C #: no hay ninguna restricción de "interfaz" en el CLR.


6

Si es posible, elegí una solución como esta. Solo funciona si desea que se pasen varias interfaces específicas (por ejemplo, aquellas a las que tiene acceso a la fuente) como un parámetro genérico, no ninguno.

  • Dejé que mis interfaces, que se cuestionaron, hereden una interfaz vacía IInterface.
  • Limité el parámetro T genérico para que sea de IInterface

En origen, se ve así:

  • Cualquier interfaz que desee pasar como parámetro genérico:

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

    public interface IInterface
    {
        // Nothing in here, keep moving
    }
  • La clase en la que desea poner la restricción de tipo:

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

Esto no logra mucho. Su Tno está restringido a las interfaces, está limitado a todo lo que implemente IInterface, lo que cualquier tipo puede hacer si lo desea, por ejemplo, struct Foo : IInterfacedado que IInterfacees muy probable que sea público (de lo contrario, todo lo que acepte debería ser interno).
AnorZaken

Si de todos modos controla todos los tipos que desea aceptar, puede usar la generación de código para crear todas las sobrecargas adecuadas, todo lo cual simplemente redirige a un método privado genérico.
AnorZaken

2

Lo que ha decidido es lo mejor que puede hacer:

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

2

Intenté hacer algo similar y utilicé una solución alternativa: pensé en el operador implícito y explícito en la estructura: la idea es envolver el Tipo en una estructura que se pueda convertir en Tipo implícitamente.

Aquí hay tal estructura:

public struct InterfaceType {tipo privado _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);

Tienes que imaginar tu propio mecanismo en torno a esto, pero un ejemplo podría ser un método tomado un parámetro InterfaceType en lugar de un tipo

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

Un método para anular que debería devolver los tipos de interfaz:

public virtual IEnumerable<InterfaceType> GetInterfaces()

Quizás también haya cosas que hacer con los genéricos, pero no lo intenté

Espero que esto pueda ayudar o dar ideas :-)


0

Solución A: esta combinación de restricciones debería garantizar que TInterfacees una interfaz:

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

Requiere una sola estructura TStructcomo Testigo para demostrar que TInterfacees una estructura.

Puede usar una estructura única como testigo para todos sus tipos no genéricos:

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

Solución B: si no desea hacer estructuras como testigos, puede crear una interfaz

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

y usa una restricción:

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

Implementación para interfaces:

interface IA :ISInterface<IA>{ }

Esto resuelve algunos de los problemas, pero requiere la confianza de que nadie implementa ISInterface<T>para tipos que no son de interfaz, pero eso es bastante difícil de hacer accidentalmente.


-4

Use una clase abstracta en su lugar. Entonces, tendrías algo como:

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

10
No siempre se puede reemplazar una interfaz con una clase abstracta, ya que C # no admite la herencia múltiple.
Sam
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.