2012-12-05 2 views
14

La mia domanda è relativa a Is there a reasonable approach to "default" type parameters in C# Generics?, ma utilizzando una classe generica interna tale approccio non funziona.Posso rendere un generico facoltativo, per impostazione predefinita per una determinata classe?

codice dato in questo modo:

using System; 

public class FooEventArgs<T> : EventArgs 
{ 
    // ... T properties and a constructor 
} 

public class Foo<T> 
{ 
    public delegate void EventHandler<FooEventArgs>(object sender, FooEventArgs<T> e); 
    public event EventHandler<FooEventArgs<T>> Changed 
} 

E con esso viene utilizzato in questo modo:

public class User 
{ 
    public Foo<int> foo1; 
    public Foo<object> foo2; 

    public User() 
    { 
     foo1 = new Foo<int>(); 
     foo2 = new Foo<object>(); 
     foo1.Changed += foo1_Changed; 
     foo2.Changed += foo2_Changed; 
    } 

    protected void foo1_Changed(object sender, FooEventArgs<int> e) { ... } 
    protected void foo2_Changed(object sender, FooEventArgs<object> e) { ... } 
} 

Beh, mi piacerebbe un po 'come se avessi potuto opzionale generica, in quanto non vi sarà ci sono molti casi in cui non so di che tipo entrerà qualcosa (i dati provengono da un sistema esterno che ha i propri tipi di variabili, che vengono poi convertiti in tipi .NET, ma mi imbatto in situazioni in cui, per esempio , un tipo di dati remoto può trasformarsi in uno di un paio di tipi .NET, o in cui è del "qualsiasi" tipo-così object sarebbe l'unica vera risposta per quel caso)

La soluzione che subito mi venne in mente fu la creazione di sottoclassi (era anche il suggerimento primario nella questione legata alla prima):.

public class Foo : Foo<object> 
{ 
    public Foo(...) : base(...) { } 
} 

public class FooEventArgs : FooEventArgs<object> 
{ 
    public Foo(...) : base(...) { } 
} 

allora voglio usare in questo modo:

public class User 
{ 
    public Foo foo3; 

    public User() 
    { 
     foo3 = new Foo(); 
     foo3.Changed += foo3_Changed; 
    } 

    protected void foo3_Changed(object sender, FooEventArgs e) { ... } 
} 

Il problema è che, naturalmente, non funziona con foo3_Changed accettare FooEventArgs; ha bisogno di FooEventArgs<object>, poiché questo è ciò che l'evento Foo.Changed otterrà ad esso (poiché il valore arriverà da Foo<object>).

Foo.cs(3,1415926): error CS0123: No overload for 'foo3_Changed' matches delegate 'FooLibrary.Foo<object>.EventHandler<FooLibrary.FooEventArgs<object>>' 

C'è qualcosa che posso fare su questo, a corto di duplicare gran parte della classe?

Ho provato un'altra cosa: un operatore implicito per convertire da FooEventArgs<object> a FooEventArgs.

public static implicit operator FooEventArgs(FooEventArgs<object> e) 
    { 
     return new FooEventArgs(...); 
    } 

Questo, purtroppo, non sembra funzionare, anche se non sono del tutto chiaro il motivo per cui:

EditBuffer.cs(13,37): error CS0553: 'FooLibrary.FooEventArgs.implicit operator FooLibrary.FooEventArgs(FooLibrary.FooEventArgs<object>)': user-defined conversions to or from a base class are not allowed 

Dunque, ancora una volta, c'è qualcosa che posso fare su questo, o ho ragione nel ritenere che sia Tough Luck e dovrò semplicemente accontentarmi di usare FooEventArgs<object> (e quindi penso che possa anche usare lo Foo<object>)?

+0

[correlate] (https://github.com/dotnet/csharplang/issues/253) – Shimmy

risposta

20

Non credo che ci sia molto da fare a riguardo, ad essere onesti. Si potrebbe fare Foo doppiamente generico:

public class Foo<TData, TArgs> where TArgs : FooEventArgs<TData> 
{ 
    public delegate void EventHandler<TArgs>(object sender, TArgs e); 
    public event EventHandler<TArgs> Changed; 
} 

allora si potrebbe scrivere:

public class Foo : Foo<object, FooEventArgs> 

... ma è davvero facendo le cose molto complicato per un piccolo vantaggio.

Direi anche che anche se è un po 'più prolisso includere l'argomento tipo, lo rende molto chiaro - considerando che l'ereditarietà può confondere le acque in vari modi. Eviterei l'ereditarietà della classe quando non sei in realtà cercando di modellare la specializzazione del comportamento.

Il motivo per cui la conversione implicita non funziona non ha nulla a che fare con i generici, tra l'altro - come afferma il messaggio di errore, non è possibile dichiarare una conversione (implicita o esplicita) che sale o scende la gerarchia dell'ereditarietà . Dalla sezione specifica C# 6.4.1:

C# consente di dichiarare solo determinate conversioni definite dall'utente. In particolare, non è possibile ridefinire una conversione implicita o esplicita già esistente.

(vedere quella sezione per maggiori dettagli.)


Come nota a margine, trovo più comune per usare l'ereditarietà il contrario modo per i medicinali generici, in genere con le interfacce:

public interface IFoo 
{ 
    // Members which don't depend on the type parameter 
} 

public interface IFoo<T> : IFoo 
{ 
    // Members which all use T 
} 

Quel codice modo può ricevere solo un IFoo senza preoccuparsi del lato generici delle cose, se non hanno bisogno di sapere T.

Sfortunatamente, questo non ti aiuta nel tuo caso specifico.

+0

Questo è un interessante spaccato sul modo inverso di trattare con l'eredità di interfaccia . Ma come osservi, ciò non funzionerà in questo caso. Grazie. –

+0

@ChrisMorgan: Sì, l'ho portato solo come argomento secondario. Ho modificato l'inizio della risposta con un'altra idea, ma non è quella che effettivamente mi piace *. –

+0

No, non mi piace neanche ... beh allora, esplicito penso che lo sia. –

8

Un'altra cosa interessante che ho appena trovato è che è possibile creare classi generiche con lo stesso nome ma diverse firme.

class Foo<T> { 

} 

class Foo<T,T> { 

} 

quindi è possibile chiamare uno di loro come segue:

new Foo<string>(); 
new Foo<int,double>(); 
new Foo<string,int>(); 

ho solo pensato che fosse interessante il fatto che, nonostante entrambe le classi con lo stesso nome possono coesistere perché hanno firme diverse.

Credo che questo è come funziona la classe Tuple