2010-10-01 5 views
8

La classe StringBuilder consente, in quello che io ritengo essere un modo molto intuitivo, il metodo catena di chiamate a .Append(), .AppendFormat() e alcuni altri in questo modo:C# Sovraccarico generico dell'elenco <T>: Come si farebbe?

StringBuilder sb = new StringBuilder(); 
sb.Append("first string") 
    .Append("second string); 

La classe List Il metodo .Add(), d'altra parte, restituisce void - quindi le chiamate a catena non funzionano. Questo, a mio avviso e le parole immortali di Jayne Cobb "non fanno proprio senso".

Ammetto che la mia comprensione di Generics è molto semplice, ma vorrei sovraccaricare il metodo .Add() (e altri) in modo da restituire l'oggetto originale e consentire il concatenamento. Qualsiasi assistenza verrà ricompensata con ulteriori citazioni di Firefly.

risposta

13

Se si desidera mantenere lo stesso nome per il metodo Add, si potrebbe nascondere il metodo dalla classe base:

public class MyList<T> : List<T> 
{ 
    public new MyList<T> Add(T item) 
    { 
     base.Add(item); 
     return this; 
    } 
} 

Tuttavia, questo funziona solo se si sta manipolando la lista con una variabile digitata esplicitamente come MyList<T> (ovvero non funzionerà se la variabile è dichiarata come IList<T> per esempio). Quindi penso che le soluzioni che prevedono un metodo di estensione siano migliori, anche se ciò significa cambiare il nome del metodo.

Anche se altri hanno già postato soluzioni con metodi di estensione, eccone un altro, che ha il vantaggio di conservare il tipo effettivo di raccolta:

public static class ExtensionMethods 
{ 
    public static TCollection Append<TCollection, TItem>(this TCollection collection, TItem item) 
     where TCollection : ICollection<TItem> 
    { 
     collection.Add(item); 
     return collection; 
    } 
} 

utilizzarlo come che:

var list = new List<string>(); 
list.Append("Hello").Append("World"); 
+0

Questa è stata la risposta più completa, che è stata pubblicata per prima: sono tutti molto simili, quindi era difficile scegliere quale risposta. Grazie a tutti per la discussione: ho imparato molto. –

2
public static IList<T> Anything-not-Add*<T>(this IList<T> list, T item) 
{ 
    list.Add(item); 
    return list; 
} 

*AddItem, Append, AppendList, ecc (vedi commenti qui sotto)

La stessa idea è venuta in mente come gli altri ragazzi troppo, indipendentemente:

public static TList Anything<TList, TItem>(this TList list, TItem item) 
    where TList : IList<TItem> 
{ 
    list.Add(item); 
    return list; 

}

E Thomas ha ragione: per quanto riguarda IList<T> eredita ICollection<T> dovresti usare IColl essione.

+1

Metodi di estensione per la vittoria! Questo funzionerà solo in C# 3 o versioni successive (quindi VS 2008 o 2010). –

+4

questo non funzionerà, perché durante il sovraccarico il compilatore preferirà il metodo di istanza all'estensione – desco

+0

Questo è un metodo di estensione, giusto? Come saprà il compilatore utilizzare questa versione del metodo anziché l'originale (@desco sembra stia dicendo che non lo farà) –

5

uso può creare metodo di estensione

public static class ListExtensions 
{ 
    public static List<T> AddItem<T>(this List<T> self, T item) 
    { 
     self.Add(item); 
     return self; 
    } 
} 

var l = new List<int>(); 
l.AddItem(1).AddItem(2); 

EDIT

possiamo anche rendere questo metodo generico su parametro raccolta

public static class ListExtensions 
{ 
    public static TC AddItem<TC, T>(this TC self, T item) 
     where TC : ICollection<T> 
    { 
     self.Add(item); 
     return self; 
    } 
} 

var c1 = new Collection<int>(); 
c1.AddItem(1).AddItem(2); 

var c2 = new List<int>(); 
c2.AddItem(10).AddItem(20); 

EDIT 2: Forse qualcuno troverà questo trucco utile, è possibile uti inizializzatore di oggetti nidificati di lize e inizializzatore di raccolta per l'impostazione di proprietà e l'aggiunta di valori in istanze esistenti.

using System; 
using System.Collections.Generic; 
using System.Linq; 

struct I<T> 
{ 
    public readonly T V; 
    public I(T v) 
    { 
     V = v; 
    } 
} 

class Obj 
{ 
    public int A { get; set; } 
    public string B { get; set; } 

    public override string ToString() 
    { 
     return string.Format("A={0}, B={1}", A, B); 
    } 
} 


class Program 
{ 
    static void Main() 
    { 
     var list = new List<int> { 100 }; 
     new I<List<int>>(list) 
      { 
       V = { 1, 2, 3, 4, 5, 6 } 
      }; 

     Console.WriteLine(string.Join(" ", list.Select(x => x.ToString()).ToArray())); // 100 1 2 3 4 5 6 

     var obj = new Obj { A = 10, B = "!!!" }; 
     Console.WriteLine(obj); // A=10, B=!!! 
     new I<Obj>(obj) 
      { 
       V = { B = "Changed!" } 
      }; 
     Console.WriteLine(obj); // A=10, B=Changed! 
    } 
} 
+0

Non c'è modo di farlo senza cambiare il nome del metodo? Mi piacerebbe per ridurre al minimo possibile confusione in altri sviluppatori –

+0

Si dovrebbe dichiarare il parametro come ICollection anziché Lista

+0

@Thomas: 'add()' è il metodo di entrambi IList ICollection e – abatishchev

1

Avere un metodo di estensione off:

public static List<T> Append(this List<T> list, T item) 
{ 
    list.Add(item); 
    return self; 
} 

Nota che dobbiamo crearlo con un nuovo nome, come se un membro di istanza corrisponde la firma ('Aggiungi' si sta già lamenti) quindi il metodo di estensione non verrà chiamato.

In ogni caso, mi raccomando contro questo. Mentre mi piace incatenarmi, è raro che nelle librerie C# non sia così idiomatico come in altre lingue dove è più comune (nessuna ragione tecnica per questo, anche se alcune differenze nel modo in cui funzionano le proprietà la incoraggia un po 'di più in alcune altre lingue , proprio come le cose sono in termini di ciò che è comune). Per questo motivo, i costrutti che abilita non sono così familiari in C# come altrove, e il tuo codice ha più probabilità di essere frainteso da un altro dev.

0

Mi piace l'approccio di estensione che altri hanno menzionato in quanto sembra rispondere bene alla domanda (anche se dovresti dargli una firma di metodo diversa rispetto a quella esistente()). Inoltre, sembra che ci sia qualche incoerenza sui ritorni di oggetti su chiamate come questa (ho pensato che fosse un problema di mutevolezza, ma il costruttore di stringhe è mutabile no?), Quindi sollevi una domanda interessante.

Sono curioso, però, se il metodo AddRange non avrebbe funzionato come una soluzione out-of-the-box? C'è una ragione particolare per cui desideri concatenare i comandi anziché passare tutto come una matrice?

Farebbe qualcosa del genere non realizzare ciò che ti serve?

List<string> list = new List<string>(); 
list.AddRange(new string[]{ 
    "first string", 
    "second string", 
}); 
1

Si potrebbe utilizzare un metodo di estensione con un nome diverso:

public static T Put<T, U>(this T collection, U item) where T : ICollection<U> { 
    collection.Add(item); 
    return collection; 
} 

Per creare il codice come questo:

var list = new List<int>(); 
list.Put(1).Put(2).Put(3); 

Per mantenere il nome Add, tuttavia, si può avere un metodo come questo:

public static T Add<T, U>(this T collection, Func<U> itemProducer) 
    where T : ICollection<U> { 
    collection.Add(itemProducer()); 
    return collection; 
} 

E creare il codice come questo:

list.Add(()=>1).Add(()=>2).Add(()=>3); 

non sembra che buona.

Forse se cambiamo il tipo che può avere una sintassi migliore.

Data questa classe:

public class ListBuilder<T> { 
    IList<T> _list; 
    public ListBuilder(IList<T> list) { 
    _list = list; 
    } 
    public ListBuilder<T> Add(T item) { 
    _list.Add(item); 
    return this; 
    } 
} 

si può avere questo metodo:

public static ListBuilder<T> Edit<T>(this IList<T> list) { 
    return new ListBuilder<T>(list); 
} 

E utilizzare il codice come questo:

list.Edit().Add(1).Add(2).Add(3); 
+1

soluzione molto bella! –

1

Sono sicuro che non apprezzerà questa risposta, ma c'è una buona ragione per cui l'elenco <> .Add() funziona in questo modo. È molto veloce, deve essere competitivo con un array e perché è un metodo di basso livello.È comunque solo un pelo troppo grande per essere sottolineato dall'ottimizzatore JIT. Non è possibile ottimizzare la dichiarazione di reso di cui avresti bisogno per restituire il riferimento alla lista.

La scrittura lst.Add (obj) nel codice è gratuita, il primo riferimento è disponibile in un registro CPU.

Una versione di Add() che restituisce il riferimento rende il codice quasi il 5% più lento. È molto peggio per il metodo di estensione proposto, c'è un intero stack in più.

+0

Sono sicuro che non apprezzerai questo commento, ma forse potresti spiegare perché non apprezzerei quella risposta. E 'stato ben scritto, facile da capire e ha sollevato alcuni punti positivi sull'ottimizzazione. –