Sto lavorando per riscrivere la mia interfaccia fluente per la mia libreria di classi IoC, e quando ho refactored del codice per condividere alcune funzionalità comuni attraverso una classe base, ho trovato un problema.Inferenza di tipo generico parziale possibile in C#?
Nota: Questo è qualcosa che voglio da fare, non qualcosa che devo da fare. Se dovessi accontentarmi di una sintassi diversa, lo farò, ma se qualcuno ha un'idea su come compilare il mio codice nel modo in cui lo voglio, sarebbe molto gradito.
Desidero che alcuni metodi di estensione siano disponibili per una classe base specifica e questi metodi dovrebbero essere generici, con un tipo generico, relativi a un argomento del metodo, ma i metodi dovrebbero anche restituire un tipo specifico relativo a il particolare discendente su cui sono invocati.
Meglio con un esempio di codice rispetto alla descrizione di cui sopra methinks.
Ecco un esempio semplice e completa di ciò che non lavoro:
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
}
}
}
Se si compila questo, si otterrà:
'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'...
Quello che voglio è per l'estensione metodo (Parameter<T>
) per poter essere richiamato su entrambi ConcreteTypeRegistration
e DelegateRegistration
e, in entrambi i casi, il tipo di reso deve corrispondere al tipo in cui è stata richiamata l'estensione.
Il problema è come segue:
desidero scrivere:
ct.Parameter<string>("name", "Lasse")
^------^
notice only one generic argument
ma anche che Parameter<T>
restituisce un oggetto dello stesso tipo è stato richiamato sopra, che significa:
ct.Parameter<string>("name", "Lasse").SomethingConcrete();
^ ^-------+-------^
| |
+---------------------------------------------+
.SomethingConcrete comes from the object in "ct"
which in this case is of type ConcreteTypeRegistration
C'è un modo per ingannare il compilatore nel fare questo salto per me?
Se aggiungo due argomenti di tipo generico al metodo Parameter
, inferenza di tipo mi costringe a uno fornire sia, o nessuno, che significa che questo:
public static TReg Parameter<TReg, T>(
this TReg p, string name, T value)
where TReg : ParameterizedRegistrationBase
mi dà questo:
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
Che è altrettanto brutto.
Posso facilmente ristrutturare le classi, o anche rendere i metodi metodi non di estensione introducendoli nella gerarchia, ma la mia domanda è se posso evitare di dover duplicare i metodi per i due discendenti, e in qualche modo dichiarali solo una volta, per la classe base.
Lasciatemi riformulare. C'è un modo per cambiare le classi nel primo esempio di codice sopra, in modo che la sintassi nel metodo Main possa essere mantenuta, senza duplicare i metodi in questione?
Il codice dovrà essere compatibile sia con C# 3.0 che con 4.0.
Edit: Il motivo per cui preferisco non lasciare entrambi gli argomenti di tipo generico per inferenza è che per alcuni servizi, voglio specificare un valore di parametro per un parametro costruttore che è di un tipo, ma passaggio in un valore che è un discendente. Per il momento, la corrispondenza dei valori degli argomenti specificati e il costruttore corretto da chiamare viene eseguita utilizzando sia il nome che il tipo dell'argomento.
Vi faccio un esempio:
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
Se lascio sia per inferenza di tipo, il tipo di parametro sarà FileStream
, non Stream
.
Sì, sembra la soluzione migliore. Ne sto testando uno ora dove ho introdotto anche i generici nella classe base, e ho appena eliminato i metodi di estensione, ma il tuo esempio mi consente di mantenerli come metodi di estensione, che io preferisco in questo caso. Cioè, i due metodi di estensione che chiamano solo uno comune specificando i tipi corretti. –