2010-04-11 16 views
20

Mi piacerebbe creare molti metodi di estensione per alcune classi generiche, ad es. perPerché è impossibile dichiarare i metodi di estensione in una classe statica generica?

public class SimpleLinkedList<T> where T:IComparable 

E ho iniziato a creare metodi come questo:

public static class LinkedListExtensions 
{ 
    public static T[] ToArray<T>(this SimpleLinkedList<T> simpleLinkedList) where T:IComparable 
    { 
     //// code 
    } 
} 

Ma quando ho cercato di fare classe LinkedListExtensions generico come questo:

public static class LinkedListExtensions<T> where T:IComparable 
{ 
    public static T[] ToArray(this SimpleLinkedList<T> simpleLinkedList) 
    { 
     ////code 
    } 
} 

ottengo "metodi estensione può essere dichiarato solo in una classe statica non generica, non annidata ".

E sto cercando di indovinare da dove viene questa restrizione e non ho idee.

MODIFICA: Non hanno ancora una visione chiara del problema. Sembra che questo non sia stato implementato per qualche motivo.

+0

@Dan Bryant, sono consentite classi statiche generiche. Solo non per le classi che contengono metodi di estensione. – Dykam

+0

Oltre alla domanda ... Perché nel tuo primo esempio è .ToArray() definito come metodo di estensione? Se è nella classe SimpleLinkedList sicuramente puoi semplicemente definire un metodo pubblico T [] ToArray() lì. –

+0

Probabilmente per semplificare la scrittura di compilatori (non solo il compilatore C#) in quanto riduce il numero di condizioni da controllare. –

risposta

0

Solo un pensiero, ma c'è qualche ragione per cui non puoi derivare da questa classe e aggiungere i metodi extra alla specializzazione piuttosto che scrivere una suite di metodi di estensione? Sono sicuro che hai le tue ragioni, ma buttale lì fuori.

1

Una domanda molto interessante, non sono mai stato tentato di utilizzare classi generiche statiche, ma almeno sembra possibile.

Nel contesto della dichiarazione dei metodi di estensione, è possibile non solo dichiarare i metodi di estensione per un determinato tipo generico (ad esempio IEnumerable<T>) ma inserire anche il parametro di tipo T nell'equazione. Se accettiamo di trattare IEnumerable<int> e IEnumerable<string> come tipi diversi, questo ha senso anche a livello concettuale.

La possibilità di dichiarare i propri metodi di estensione in una classe generica statica consente di risparmiare ripetendo i vincoli dei parametri di tipo più e più volte, raggruppando tutti i metodi di estensione per IEnumerable<T> where T : IComparable insieme.

In base alle specifiche (citazione necessaria), i metodi di estensione possono essere dichiarati solo in classi statiche non nidificate e non generiche. Il motivo dei primi due vincoli è abbastanza ovvio:

  1. Non può portare nessuno stato in quanto non è un mixin, solo zucchero sintattico.
  2. Deve avere lo stesso ambito lessicale del tipo per cui fornisce estensioni.

La restrizione sulle classi statiche non generiche mi sembra un po 'arbitraria, non riesco a trovare una ragione tecnica qui. Ma potrebbe essere che i progettisti linguistici abbiano deciso di scoraggiarti nella scrittura di metodi di estensione che dipendono dal parametro tipo di una classe generica per la quale desideri fornire metodi di estensione. Invece vogliono che forniate un'implementazione veramente generica del vostro metodo di estensione, ma rendono possibile fornire versioni ottimizzate/specializzate (in termini di tempo di compilazione) dei vostri metodi di estensione oltre alla vostra implementazione generale.

Ricordami la specializzazione del modello in C++. EDIT: Purtroppo questo è sbagliato, per favore vedi le mie aggiunte qui sotto.


Ok, poiché questo è un argomento molto interessante ho fatto qualche ulteriore ricerca.In realtà c'è una restrizione tecnica che mi è sfuggita qui. Diamo un'occhiata a qualche codice:

public static class Test 
{ 
    public static void DoSomething<T>(this IEnumerable<T> source) 
    { 
     Console.WriteLine("general"); 
    } 
    public static void DoSomething<T>(this IEnumerable<T> source) where T :IMyInterface 
    { 
     Console.WriteLine("specific"); 
    } 
} 

Questo effettivamente riuscire con questo errore di compilazione:

Type 'ConsoleApplication1.Test' already defines a member called 'DoSomething' with the same parameter types

Ok, dopo ci prova a dividerla in due diverse classi estensioni:

public interface IMyInterface { } 
public class SomeType : IMyInterface {} 

public static class TestSpecific 
{ 
    public static void DoSomething<T>(this IEnumerable<T> source) where T : IMyInterface 
    { 
     Console.WriteLine("specific"); 
    } 
} 
public static class TestGeneral 
{ 
    public static void DoSomething<T>(this IEnumerable<T> source) 
    { 
     Console.WriteLine("general"); 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var general = new List<int>(); 
     var specific = new List<SomeType>(); 

     general.DoSomething(); 
     specific.DoSomething(); 

     Console.ReadLine(); 
    } 
} 

Contro la mia impressione iniziale (era ieri sera inoltrata), ciò si tradurrebbe in un'ambiguità nei siti di chiamata. Per risolvere questa ambiguità è necessario chiamare il metodo di estensione in modo tradizionale, ma questo è contro la nostra intenzione.

Quindi questo ci lascia una situazione in cui non è possibile dichiarare le specializzazioni generiche legate ai tempi di compilazione dei metodi di estensione. D'altra parte, non c'è ancora alcun motivo per cui non è stato possibile dichiarare i metodi di estensione solo per un singolo parametro di tipo generico speciale. Quindi sarebbe bello dichiararli in una classe generica statica.

D'altra parte la scrittura di un metodo di estensione del tipo:

public static void DoSomething<T>(this IEnumerable<T> source) where T : IMyInterface {} 

o

public static void DoSomething(this IEnumerable<IMyInterface> source) {} 

non è tutto troppo diverso e richiede solo un po 'di fusione sul sito chiamata contro sull'estensione lato metodo (probabilmente applicherete un'ottimizzazione che dipende dal tipo specifico, quindi dovrete lanciare T su IMyInterface o comunque). Quindi l'unica ragione per cui riesco a venire è, ancora una volta, che i designer linguistici vogliono incoraggiarti a scrivere estensioni generiche solo in un modo veramente generico.

Qui potrebbero accadere alcune cose interessanti se prendiamo la contravarianza nell'equazione, che stanno per essere introdotte con C# 4.0.

3

Il problema è: come fa il compilatore a risolvere l'estensione?

Diciamo che definire sia i metodi che descrivono:

public static class LinkedListExtensions { 
    public static T[] ToArray<T>(this SimpleLinkedList<T> simpleLinkedList) where T:IComparable { 
     //// code 
    } 
} 
public static class LinkedListExtensions<T> where T:IComparable { 
    public static T[] ToArray(this SimpleLinkedList<T> simpleLinkedList) { 
     ////code 
    } 
} 

Quale metodo viene utilizzato nel seguente caso?

SimpleLinkedList<int> data = new SimpleLinkedList<int>(); 
int[] dataArray = data.ToArray(); 

La mia ipotesi è che i progettisti di linguaggi hanno deciso di limitare i metodi di estensione ai tipi non generici per evitare questo scenario.

+0

Per quanto riguarda i miei test ha dimostrato che il compilatore può risolvere questo! Non ha nulla a che fare con la classe statica generica. –

+0

@Johannes Rudolph: E quale metodo sceglierebbe? Non è affatto ovvio per me. – Gorpik

+0

@Johannes Rudolph - Che test hai fatto? La domanda iniziale riguardava il modo in cui la seconda classe dell'esempio non viene compilata, quindi come è stata compilata? –

13

In generale, dal momento che non si specifica la classe quando si utilizza un metodo di estensione, il compilatore non avrebbe modo di sapere quale è la classe in cui è definito il metodo di estensione:

static class GenStatic<T> 
{ 
    static void ExtMeth(this Class c) {/*...*/} 
} 

Class c = new Class(); 
c.ExtMeth(); // Equivalent to GenStatic<T>.ExtMeth(c); what is T? 

Dal metodi di estensione stessi possono essere generico, questo non è un vero problema a tutti:

static class NonGenStatic 
{ 
    static void GenExtMeth<T>(this Class c) {/*...*/} 
} 

Class c = newClass(); 
c.ExtMeth<Class2>(); // Equivalent to NonGenStatic.ExtMeth<Class2>(c); OK 

si può facilmente riscrivere il vostro esempio in modo che la classe statica non è generica, ma i metodi sono generici. In effetti, questo è il modo in cui vengono scritte le classi .NET come Enumerable.

public static class LinkedListExtensions 
    { 
    public static T[] ToArray<T>(this SimpleLinkedList<T> where T:IComparable simpleLinkedList) 
    { 
     // code 
    } 
    } 
+0

Un buon punto sul fatto che i metodi di estensione non generici su classi statiche generiche potrebbero causare mal di testa. –

+0

Se il metodo di estensione non utilizzava membri statici della classe in cui è contenuto, non importa quale classe è stata scelta. Potresti aumentare il tuo esempio avendo il metodo di estensione usare qualcosa come un campo di classe statico; la scelta del campo sarebbe chiaramente semanticamente rilevante, ma non ci sarebbe alcun modo per il compilatore di selezionarlo. – supercat

+0

@supercat: non importa. Il compilatore non può scegliere una classe qualsiasi: deve sapere quale classe specifica scegliere, anche se l'implementazione del metodo è esattamente la stessa per tutti. – Gorpik

2

Non pensare ai metodi di estensione come vincolati alla classe statica in cui sono contenuti. Invece, pensa che siano legati a uno spazio dei nomi specifico. Pertanto, la classe statica in cui sono definiti è semplicemente una shell utilizzata per dichiarare questi metodi all'interno dello spazio dei nomi. E benché tu possa benissimo scrivere più classi per diversi tipi di metodi di estensione, non dovresti pensare alla classe stessa come a qualcosa di più di un modo per raggruppare chiaramente i tuoi metodi di estensione.

I metodi di estensione non estendono alcun attributo della classe in cui sono contenuti. La firma del metodo definirà tutto ciò che riguarda il metodo di estensione. E anche se puoi chiamare il tuo metodo in questo modo, LinkedListExtensions.ToArray(...), non credo che sia stato l'intento dei metodi di estensione. Pertanto, ritengo che i creatori di framework abbiano probabilmente creato la restrizione in cui si è imbattuto in un modo per informare gli sviluppatori che i metodi di estensione sono autonomi e non sono direttamente legati alla classe in cui risiedono.

-1

Le classi statiche dovrebbero poter essere definite come astratte. Se fossero in grado di farlo, allora potreste specificare che non potrebbero essere usati direttamente, ma devono essere ereditati come una normale classe e quindi le classi statiche generiche sarebbero fattibili per i metodi di estensione perché potreste definirli nella classe astratta, eredita da detta classe e tutto funzionerebbe correttamente.

Come è ora, ci sono un sacco di limiti significativi con le classi statiche, questo è solo uno che mette veramente a disagio con il mojo in molti casi. In combinazione con l'impossibilità di avere il compilatore di inferire il tipo restituito e quindi di dover inserire l'intera implementazione generica per chiamare funzioni generiche che non hanno tutti i generici nella firma del metodo rende questa roba eccessivamente debole e rende molto digitando che non dovrebbe essere necessario.

(C'è anche il caso in cui il secondo generico è definito nella definizione del primo e non lo userà, anche se è ovvio dedurre anche in fase di compilazione. Questo è super fastidioso perché è completamente ovvio.) MS ha un sacco di lavoro su Generics che potrebbero fare per migliorare questa roba.

Problemi correlati