Parametri di tipo generico in cui uno è esso stesso generico con un parametro di tipo dell'altro

1

Ho un paio di classi con il seguente schema con cui mi sento a disagio. È questo l'unico modo per farlo, o sono gli altri modelli di progettazione che sarebbero più affidabili.

public class MyClass1<T,K> where T: MyClass2<K>
{
...
public MyClass1(K param1) {...} // example of constructor, other constructors do not use K.
...
}

Ritengo che dovrebbe essere possibile per MyClass1 calcolare K da T, in modo che non debba essere passato dall'esterno. Tuttavia ho rivisitato questa classe molte volte e mentre funziona e non riesco a individuare un altro modo per farlo, non mi sento a mio agio con esso. Sembra aperto ai programmatori che utilizzano questa classe non essendo sicuro di quale dovrebbe essere il secondo parametro di tipo.

T non ha bisogno di essere generico, ma ereditato da MyClass2. Infatti nel mio esempio di questo MyClass2 è astratto e tutte le classi ereditate da esso non sono generiche. Non mi piace che il programmatore che consuma MyClass1 abbia bisogno di conoscere il parametro type della classe base generica sottostante qualunque cosa stiano usando per T.

La cosa buona è che il codice che consuma non verrà compilato se K non è il tipo giusto da usare con T. Ma sembra ancora aperto alla confusione quando si tenta di consumare questa classe.

Quindi c'è un modo in cui questo può essere fatto dove c'è un solo parametro di tipo e l'altro viene elaborato da esso?

Ecco l'esempio concreto. Se la risposta è che questo non è nel complesso il design giusto, sono aperto ai commenti su quelle righe.

Ho iniziato con la classe Enumeration di LosTechies qui , ma volevo che il valore fosse essere qualcosa di diverso da un int, e in effetti aveva bisogno che la classe Enumeration fosse generica, quindi il tipo di valore poteva essere specificato diversamente in momenti diversi. (Nota: questa è la sintassi C # 6.0).

public abstract class Enumeration<K> : IComparable where K : IComparable, IEquatable<K>
{
protected K Value { get; }
public string DisplayName { get; }

... // other methods from the LosTechies enumerator class.

    private static T parse<T, V>(V value, string description, Func<T, bool> predicate) where T : Enumeration<K>, new()
{
    var matchingItem = GetAll<T>().FirstOrDefault(predicate);

    if (matchingItem == null)
    {
        //var message = string.Format("'{0}' is not a valid {1} in {2}", value, description, typeof(T));
        //throw new ApplicationException(message);

        throw new InvalidEnumerationException<T,K>(value.ToString(),description);
    }

    return matchingItem;
}

Non mi piaceva l'uso di ApplicationException nel codice LosTechies, poiché capisco che Microsoft non ne consiglia più l'uso. Quindi voglio creare la mia eccezione più specifica che può generare il messaggio di eccezione dato il valore non valido.

public class InvalidEnumerationException<T,K> : Exception, ISerializable
    where T : Enumeration<K>
    where K: IComparable, IEquatable<K>
{
public InvalidEnumerationException(K value, string description)
    :base(@"{value.ToString()}' is not a valid {description} in {typeof(t)}")
{}
... // other standard exception constructors
}

L'ho semplificato in qualche modo poiché ho apportato alcune altre modifiche dalla classe di enumerazione originale che non ritengo siano rilevanti qui. Spero che questo catturi le parti rilevanti per quello che sto facendo.

Ciò che non mi piace è che il consumo di InvalidEnumerationException richiede di passare separatamente un parametro di tipo di Enumerazione AND K. Le classi che ereditano dall'enumerazione generalmente NON SONO generiche, ma basate su una firma fissa di Enumerazione.

Durante la preparazione di questo concreto esempio, ho rilevato che InvalidEnumerationException viene sollevata dall'Enumerazione, pertanto questo conosce già K. Il problema si verifica solo quando InvalidEnumerationException viene generato all'esterno dell'Enumeration. È probabile ... beh, finora solo nei miei Test unitari di InvalidEnumerationException. Tuttavia posso prevedere alcune classi derivate da Enumeration, o una derivata da una classe derivata da Enumeration può volere avere la propria implementazione dei metodi FromValue / FromDescription dell'enumerazione che lanciano questa eccezione da soli.

Sembra davvero che ci sia qualcosa che non va nel design quando ci sono due parametri di tipo e uno di essi deve corrispondere al parametro type dell'altro. Sembra che dovrei essere in grado di passare semplicemente T, nel mio esempio basta passare l'enumerazione.

    
posta RosieC 13.12.2015 - 01:37
fonte

0 risposte

Leggi altre domande sui tag