2011-08-01 21 views
9

Tengo dos clases de contratos de negocios:delegados de acción, los genéricos, covarianza y contravarianza

public BusinessContract 

public Person : BusinessContract 

en otra clase Tengo el siguiente código:

private Action<BusinessContract> _foo; 

public void Foo<T>(Action<T> bar) where T : BusinessContract 
{ 
    _foo = bar; 
} 

Lo anterior ni siquiera se compile, que me desconcierta un poco Estoy obligando a que T sea BusinessContract, ¿por qué el compilador no sabe que la barra se puede asignar a _foo?

Al tratar de evitar esto, hemos intentado cambiar a la siguiente:

public void Foo<T>(Action<T> bar) where T : BusinessContract 
{ 
    _foo = (Action<BusinessContract>)bar; 
} 

Ahora el compilador es feliz, así que escribir el siguiente código en otra parte de mi solicitud:

Foo<Person>(p => p.Name = "Joe"); 

Y la aplicación explota con una InvalidCastException en tiempo de ejecución.

No lo entiendo. ¿No debería ser capaz de convertir mi tipo más específico a un tipo menos específico y asignarlo?

ACTUALIZACIÓN

Jon responde a la pregunta, así nos dieron el visto bueno para eso, pero sólo para cerrar el bucle en esto, así es como terminamos la solución del problema.

private Action<BusinessContract> _foo; 

public void Foo<T>(Action<T> bar) where T : BusinessContract 
{ 
    _foo = contract => bar((T)contract); 
} 

¿Por qué estamos haciendo esto? Tenemos un DAL falso que usamos para pruebas unitarias. Con uno de los métodos, debemos proporcionarle al desarrollador de la prueba la capacidad de especificar qué debe hacer el método cuando se lo llama durante la prueba (es un método de actualización que actualiza un objeto almacenado en la memoria caché de la base de datos). El propósito de Foo es establecer lo que debería suceder cuando se llama a la actualización. IOW, en otra parte de esta clase tenemos lo siguiente.

public void Refresh(BusinessContract contract) 
{ 
    if(_foo != null) 
    { 
     _foo(contract); 
    } 
} 

El desarrollador de la prueba podría entonces, por ejemplo, decidir que quería establecer el nombre a un valor diferente cuando se llamó a Refresh.

Foo<Person>(p => p.Name = "New Name"); 

Respuesta

13

Tiene la covarianza y la contradicción al revés. Consideremos Action<object> y Action<string>. La eliminación de los genéricos reales, que está tratando de hacer algo como esto:

private Action<object> _foo; 

public void Foo(Action<string> bar) 
{ 
    // This won't compile... 
    _foo = bar; 
} 

Ahora supongamos que escribimos a continuación:

_foo(new Button()); 

eso está bien, porque se puede pasar Action<object> cualquier objeto ... pero lo hemos inicializado con un delegado que debe tomar un argumento de cadena. Ay.

Esto no es seguro, por lo que no se compila.

La otra forma haría trabajo, sin embargo:

private Action<string> _foo; 

public void Foo(Action<object> bar) 
{ 
    // This is fine... 
    _foo = bar; 
} 

Ahora cuando invocamos _foo, que tenemos a pasar una serie de - pero eso está bien, porque hemos inicializado con un delegado, que puede tomar cualquier referencia object como parámetro, así que está bien que le demos una cadena.

Así que básicamente es Action<T>contravariant - mientras que Func<T> es covariante :

Func<string> bar = ...; 
Func<object> foo = bar; // This is fine 
object x = foo(); // This is guaranteed to be okay 

No está claro lo que estás tratando de hacer con la acción, por lo que lamentablemente no puede dar realmente cualquier consejo sobre cómo solucionar esto ...

+0

@MDeSchaepmeester: No, no lo estoy - en el código de trabajo (la segunda mitad de la publicación) estoy asignando un valor 'Action ' ('bar') a una variable' Action '(' _foo'), y luego asignar un valor 'Func ' ('bar)' a una variable 'Func ' ('foo'). –

+0

Tienes razón, error tonto, en realidad no leí ese código y asumí que llamabas a Foo con _foo como parámetro (Vine aquí porque estaba haciendo * eso *) – MarioDS

0

No se puede asignar porque ya está utilizando un contravariant en lugar de una covariante, no hay manera de garantizar que el tipo genérico se puede asignar a foo.

+0

Bueno, no es realmente "porque [el OP] está usando genéricos" - es porque 'Acción ' es contravariante en lugar de cov ariant –

+0

@Jon Skeet, tienes razón, no me expliqué bien, lo editaré. –

Cuestiones relacionadas