Estoy trabajando en la reescritura de mi interfaz fluida para la biblioteca de clases de IoC, y cuando refactoreé un código para compartir algunas funcionalidades comunes a través de una clase base, encontré un inconveniente.Inferencia de tipo genérico parcial posible en C#?
Nota: Esto es algo que quiero que hacer, algo que no tengo que hacer. Si tengo que conformarme con una sintaxis diferente, lo haré, pero si alguien tiene una idea sobre cómo hacer que mi código se compile de la manera que yo quiero, sería muy bienvenido.
Quiero que algunos métodos de extensión estén disponibles para una clase base específica, y estos métodos deben ser genéricos, con un tipo genérico, relacionado con un argumento del método, pero los métodos también deben devolver un tipo específico relacionado con el descendiente particular sobre el que se invoca.
Mejor con un ejemplo de código que la descripción anterior.
Aquí está un ejemplo simple y completa de lo que no trabajo:
using System;
namespace ConsoleApplication16
{
public class ParameterizedRegistrationBase { }
public class ConcreteTypeRegistration : ParameterizedRegistrationBase
{
public void SomethingConcrete() { }
}
public class DelegateRegistration : ParameterizedRegistrationBase
{
public void SomethingDelegated() { }
}
public static class Extensions
{
public static ParameterizedRegistrationBase Parameter<T>(
this ParameterizedRegistrationBase p, string name, T value)
{
return p;
}
}
class Program
{
static void Main(string[] args)
{
ConcreteTypeRegistration ct = new ConcreteTypeRegistration();
ct
.Parameter<int>("age", 20)
.SomethingConcrete(); // <-- this is not available
DelegateRegistration del = new DelegateRegistration();
del
.Parameter<int>("age", 20)
.SomethingDelegated(); // <-- neither is this
}
}
}
Si compila esto, se obtendrá:
'ConsoleApplication16.ParameterizedRegistrationBase' does not contain a definition for 'SomethingConcrete' and no extension method 'SomethingConcrete'...
'ConsoleApplication16.ParameterizedRegistrationBase' does not contain a definition for 'SomethingDelegated' and no extension method 'SomethingDelegated'...
Lo que quiero es que la extensión método (Parameter<T>
) para que se pueda invocar tanto en ConcreteTypeRegistration
como en DelegateRegistration
, y en ambos casos, el tipo de devolución debe coincidir con el tipo en que se invocó la extensión.
El problema es el siguiente:
me gustaría escribir:
ct.Parameter<string>("name", "Lasse")
^------^
notice only one generic argument
sino también que Parameter<T>
devuelve un objeto del mismo tipo que fue invocada en adelante, lo que significa:
ct.Parameter<string>("name", "Lasse").SomethingConcrete();
^ ^-------+-------^
| |
+---------------------------------------------+
.SomethingConcrete comes from the object in "ct"
which in this case is of type ConcreteTypeRegistration
¿Hay alguna manera de engañar al compilador para que haga este salto por mí?
Si añado dos argumentos de tipo genérico con el método Parameter
, la inferencia de tipos me obliga a cualquiera de proporcionar ambas, o ninguna, lo que significa esto:
public static TReg Parameter<TReg, T>(
this TReg p, string name, T value)
where TReg : ParameterizedRegistrationBase
me da esto:
Using the generic method 'ConsoleApplication16.Extensions.Parameter<TReg,T>(TReg, string, T)' requires 2 type arguments
Using the generic method 'ConsoleApplication16.Extensions.Parameter<TReg,T>(TReg, string, T)' requires 2 type arguments
Lo cual es igual de malo.
Puedo reestructurar fácilmente las clases, o incluso hacer que los métodos no sean de extensión introduciéndolos en la jerarquía, pero mi pregunta es si puedo evitar tener que duplicar los métodos para los dos descendientes, y de alguna manera declararlos solo una vez, para la clase base.
Déjenme reformular eso. ¿Hay alguna manera de cambiar las clases en el primer ejemplo de código anterior, para que se pueda conservar la sintaxis en el método principal, sin duplicar los métodos en cuestión?
El código deberá ser compatible con C# 3.0 y 4.0.
Editar: La razón por la que prefiero no dejo los dos argumentos de tipo genérico a la inferencia es que para algunos servicios, quiero especificar un valor de parámetro para un parámetro de constructor que es de un tipo, pero pase en un valor que es un descendiente Por el momento, la coincidencia de los valores de argumento especificados y el constructor correcto para llamar se hace utilizando tanto el nombre como el tipo del argumento.
te voy a dar un ejemplo:
ServiceContainerBuilder.Register<ISomeService>(r => r
.From(f => f.ConcreteType<FileService>(ct => ct
.Parameter<Stream>("source", new FileStream(...)))));
^--+---^ ^---+----^
| |
| +- has to be a descendant of Stream
|
+- has to match constructor of FileService
Si dejo a los dos a la inferencia de tipos, el tipo de parámetro habrá FileStream
, no Stream
.
Sí, parece la mejor solución. Estoy probando uno ahora en el que también introduje los genéricos en la clase base, y acabo de abandonar los métodos de extensión, pero su ejemplo me permite mantenerlos como métodos de extensión, que a mi favor en este caso. Es decir, los dos métodos de extensión que solo llaman uno común que especifica los tipos correctos. –