C # Generics no permitirá restricciones de tipo delegado

79

¿Es posible definir una clase en C # tal que

class GenericCollection<T> : SomeBaseCollection<T> where T : Delegate

No pude por mi vida lograr esto anoche en .NET 3.5. Intenté usar

delegate, Delegate, Action<T> and Func<T, T>

Me parece que esto debería estar permitido de alguna manera. Estoy tratando de implementar mi propio EventQueue.

Terminé haciendo esto [aproximación primitiva, fíjate].

internal delegate void DWork();

class EventQueue {
    private Queue<DWork> eventq;
}

Pero luego pierdo la capacidad de reutilizar la misma definición para diferentes tipos de funciones.

Pensamientos

Nicolás Mancuso
fuente

Respuestas:

66

Varias clases no están disponibles como restricciones genéricas, siendo Enum otra.

Para los delegados, lo más cercano que puede obtener es ": class", quizás usando la reflexión para verificar (por ejemplo, en el constructor estático) que la T es un delegado:

static GenericCollection()
{
    if (!typeof(T).IsSubclassOf(typeof(Delegate)))
    {
        throw new InvalidOperationException(typeof(T).Name + " is not a delegate type");
    }
}
Marc Gravell
fuente
8
+1 para: 1) usar el constructor estático y 2) incluir un mensaje detallado debido a las extrañas condiciones de depuración que rodean la inicialización del tipo.
Sam Harwell
6
@MarcGravell: No arrojar una excepción en un inicializador estático viola CA1065: Do not raise exceptions in unexpected locations... Siempre asumí que debería usar una regla de análisis de código personalizado para encontrar usos no válidos de su clase que normalmente no están disponibles en tiempo de ejecución.
myermian
3
A partir de C # 7.3 (publicado en mayo de 2018), se permite restringir de esta manera where T : Delegate, (y alguien publicó una nueva respuesta sobre eso a continuación).
Jeppe Stig Nielsen
16

Sí, es posible en C # 7.3, las restricciones familiares aumentaron para incluir Enum, Delegatey unmanagedtipos. Puedes escribir este código sin problema:

void M<D, E, T>(D d, E e, T* t) where D : Delegate where E : Enum where T : unmanaged
    {

    }

De Docs :

A partir de C # 7.3, puede usar la restricción no administrada para especificar que el parámetro de tipo debe ser un tipo no administrado que no acepte valores NULL. La restricción no administrada le permite escribir rutinas reutilizables para trabajar con tipos que pueden manipularse como bloques de memoria.

Enlaces útiles:

El futuro de C # , de Microsoft Build 2018

¿Qué hay de nuevo en C # 7.3?

mshwf
fuente
Sí, es posible en C # 7.3 (desde mayo de 2018) y puede ver las notas de la versión aquí .
Jeppe Stig Nielsen
13

Editar: En estos artículos se proponen algunas soluciones alternativas:

http://jacobcarpenters.blogspot.com/2006/06/c-30-and-delegate-conversion.html

http://jacobcarpenters.blogspot.com/2006_11_01_archive.html


De la especificación C # 2.0 podemos leer (20.7, Restricciones):

Una restricción de tipo de clase debe satisfacer las siguientes reglas:

  • El tipo debe ser un tipo de clase.
  • El tipo no debe estar sellado.
  • El tipo no debe ser uno de los siguientes tipos: System.Array, System.Delegate, System.Enum o System.ValueType .
  • El tipo no debe ser objeto. Debido a que todos los tipos derivan de un objeto, tal restricción no tendría ningún efecto si se permitiera.
  • Como mucho, una restricción para un parámetro de tipo dado puede ser un tipo de clase.

Y efectivamente VS2008 escupe un error:

error CS0702: Constraint cannot be special class 'System.Delegate'

Para obtener información e investigación sobre este problema, lea aquí .

Jorge Ferreira
fuente
10

Si está dispuesto a tomar una dependencia de tiempo de compilación en un IL Weaver, puede hacerlo con Fody .

Usando este complemento para Fody https://github.com/Fody/ExtraConstraints

Tu código puede verse así

public class Sample
{
    public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
    {        
    }
    public void MethodWithEnumConstraint<[EnumConstraint] T>()
    {
    }
} 

Y ser compilado a esto

public class Sample
{
    public void MethodWithDelegateConstraint<T>() where T: Delegate
    {
    }

    public void MethodWithEnumConstraint<T>() where T: struct, Enum
    {
    }
}
Simón
fuente
Enlace roto. ¿Tienes uno actual?
Justin Morgan
3

El delegado ya admite el encadenamiento. ¿No satisface esto sus necesidades?

public class EventQueueTests
{
    public void Test1()
    {
        Action myAction = () => Console.WriteLine("foo");
        myAction += () => Console.WriteLine("bar");

        myAction();
        //foo
        //bar
    }

    public void Test2()
    {
        Action<int> myAction = x => Console.WriteLine("foo {0}", x);
        myAction += x => Console.WriteLine("bar {0}", x);
        myAction(3);
        //foo 3
        //bar 3
    }

    public void Test3()
    {
        Func<int, int> myFunc = x => { Console.WriteLine("foo {0}", x); return x + 2; };
        myFunc += x => { Console.WriteLine("bar {0}", x); return x + 1; };
        int y = myFunc(3);
        Console.WriteLine(y);

        //foo 3
        //bar 3
        //4
    }

    public void Test4()
    {
        Func<int, int> myFunc = x => { Console.WriteLine("foo {0}", x); return x + 2; };
        Func<int, int> myNextFunc = x => { x = myFunc(x);  Console.WriteLine("bar {0}", x); return x + 1; };
        int y = myNextFunc(3);
        Console.WriteLine(y);

        //foo 3
        //bar 5
        //6
    }

}
Amy B
fuente
esa no es realmente la funcionalidad que estoy buscando ... Estaba tratando de hacer una restricción de tipo en mi clase genérica ...
Nicholas Mancuso
3

Me encontré con una situación en la que necesitaba lidiar con una Delegaterestricción interna pero quería una restricción genérica. Específicamente, quería agregar un controlador de eventos usando la reflexión, pero quería usar un argumento genérico para el delegado. El siguiente código NO funciona, ya que "Handler" es una variable de tipo y el compilador no se convierte Handleren Delegate:

public void AddHandler<Handler>(Control c, string eventName, Handler d) {
  c.GetType().GetEvent(eventName).AddEventHandler(c, (Delegate) d);
}

Sin embargo, puede pasar una función que realice la conversión por usted. converttoma un Handlerargumento y devuelve un Delegate:

public void AddHandler<Handler>(Control c, string eventName, 
                  Func<Delegate, Handler> convert, Handler d) {
      c.GetType().GetEvent(eventName).AddEventHandler(c, convert(d));
}

Ahora el compilador está contento. Llamar al método es fácil. Por ejemplo, adjuntar al KeyPressevento en un control de Windows Forms:

AddHandler<KeyEventHandler>(someControl, 
           "KeyPress", 
           (h) => (KeyEventHandler) h,
           SomeControl_KeyPress);

¿Dónde SomeControl_KeyPressestá el objetivo del evento? La clave es el convertidor lambda: no funciona, pero convence al compilador de que le dio un delegado válido.

(Comienza 280Z28) @Justin: ¿Por qué no usar esto?

public void AddHandler<Handler>(Control c, string eventName, Handler d) { 
  c.GetType().GetEvent(eventName).AddEventHandler(c, d as Delegate); 
} 

(Fin 280Z28)

Justin Bailey
fuente
1
@Justin: Edité tu respuesta para poner mi comentario al final ya que tiene un bloque de código.
Sam Harwell
2

Como se mencionó anteriormente, no puede tener Delegates y Enum como una restricción genérica. System.Objecty System.ValueTypetampoco se puede utilizar como una restricción genérica.

La solución puede ser si construye una llamada adecuada en su IL. Funcionará bien.

Aquí hay un buen ejemplo de Jon Skeet.

http://code.google.com/p/unconstrained-melody/

He tomado mis referencias del libro C # in Depth de Jon Skeet , tercera edición.

maxspan
fuente
1

Según MSDN

Error del compilador CS0702

La restricción no puede ser un 'identificador' de clase especial. Los siguientes tipos no pueden usarse como restricciones:

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.
Rahul Nikate
fuente
¿Por qué repite la pregunta aquí? No nos cuentas nada nuevo.
Elmue