2010-10-01 4 views
8

La classe StringBuilder vous permet, dans ce que je considère être une manière très intuitive, à la méthode de la chaîne appelle à .append(), .AppendFormat() et quelques autres comme ceci:C# Surcharge générique de la liste <T>: Comment cela serait-il fait?

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

La classe List D'autre part, la méthode .Add() renvoie void - donc les appels chaînés ne fonctionnent pas. Ceci, à mon avis, et les paroles immortelles de Jayne Cobb "ne font tout simplement pas sens".

J'avoue que ma compréhension de Generics est très basique, mais je voudrais surcharger la méthode .Add() (et d'autres) afin qu'ils retournent l'objet original, et autorisent le chaînage. Toute aide sera récompensée par d'autres citations de Firefly.

Répondre

13

Si vous voulez garder le même nom pour la méthode Add, vous pouvez cacher la méthode de la classe de base:

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

Cependant, cela ne fonctionnera que si vous manipulez la liste avec une variable explicitement tapé comme MyList<T> (ie cela ne fonctionnera pas si votre variable est déclarée comme IList<T> par exemple). Je pense donc que les solutions impliquant une méthode d'extension sont meilleures, même si cela implique de changer le nom de la méthode.

Bien que d'autres ont déjà affiché des solutions avec des méthodes d'extension, voici un autre, qui a l'avantage de conserver le type réel de la collection:

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

Utilisez comme ça:

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

C'était la réponse la plus complète, qui a été postée en premier - ils sont tous très similaires, il était donc difficile de choisir lequel comme réponse. Merci à tous pour la discussion: j'ai beaucoup appris. –

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

*AddItem, Append, AppendList, etc. (voir les commentaires ci-dessous)

La même idée est venue à mon esprit comme les autres gars aussi, indépendamment:

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

}

Et Thomas a raison: autant que IList<T> hérite ICollection<T> vous devriez utiliser IColl ection.

+1

Méthodes d'extension pour la victoire! Cela ne fonctionnera qu'en C# 3 ou plus tard (donc VS 2008 ou 2010). –

+4

cela ne fonctionnera pas, car pendant le compilateur de résolution de surcharge préférera méthode d'instance à l'extension – desco

+0

Ceci est une méthode d'extension, non? Comment le compilateur sait-il utiliser cette version de la méthode au lieu de l'original (@desco semble dire que ce ne sera pas le cas) –

5

utilisation peut créer méthode d'extension

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

nous pouvons aussi faire de cette méthode générique sur le paramètre de collection

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: Peut-être que quelqu'un va trouver cette astuce utile, il est possible de uti lize initialiseur d'objet imbriqué et initialisateur de collection pour la définition de propriétés et l'ajout de valeurs dans des instances existantes.

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

N'y a-t-il aucun moyen de le faire sans changer le nom de la méthode? Je voudrais minimiser la confusion possible dans d'autres développeurs –

+0

Vous devez déclarer le paramètre comme ICollection plutôt que la liste

+0

@Thomas: 'Ajouter()' est la méthode des deux IList et ICollection – abatishchev

1

Avoir une méthode d'extension de:

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

Notez que nous devons créer avec un nouveau nom, comme si un membre de l'instance correspond à la signature (« Add » vous êtes déjà plaignez) alors la méthode d'extension ne sera pas appelée.

Dans tous cependant, je recommanderais contre cela. Bien que j'aime être enchaîné, c'est rare dans les bibliothèques C#, ce n'est pas aussi idiomatique que dans d'autres langues où c'est plus commun (aucune raison technique, bien que certaines différences dans le fonctionnement des propriétés l'encouragent un peu plus dans d'autres langues , juste la façon dont les choses sont en termes de ce qui est commun). Pour cette raison, les constructions qu'il active ne sont pas aussi familières en C# qu'ailleurs, et votre code est plus susceptible d'être mal lu par un autre dev.

0

J'aime l'approche d'extension que d'autres ont mentionnée comme qui semble bien répondre à la question (bien que vous deviez lui donner une signature de méthode différente de celle existante Add()). En outre, il semble y avoir une certaine incohérence sur les retours d'objets sur des appels comme celui-ci (je pensais que c'était un problème de mutabilité, mais le stringbuilder est mutable n'est-ce pas?), Donc vous posez une question intéressante.

Je suis curieux, cependant, si la méthode AddRange ne fonctionnerait pas comme une solution prête à l'emploi? Y a-t-il une raison particulière pour laquelle vous voulez enchaîner les commandes au lieu de tout passer en tant que tableau?

Est-ce que faire quelque chose comme ceci n'atteindrait pas ce dont vous avez besoin?

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

Vous pouvez utiliser une méthode d'extension avec un autre nom:

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

Pour créer un code comme ceci:

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

Pour conserver le nom Add, cependant, vous pouvez avoir un méthode comme ceci:

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

Et créer un code comme ceci:

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

Il ne semble pas que bonne. Peut-être que si nous changeons le type, nous pouvons avoir une meilleure syntaxe.

Compte tenu de cette 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; 
    } 
} 

Vous pouvez avoir cette méthode:

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

et utiliser le code comme ceci:

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

très belle solution! –

1

Je suis sûr que vous ne serez pas apprécier cette réponse, mais il y a une très bonne raison que la liste <> .Add() fonctionne de cette façon. C'est très rapide, il faut être compétitif avec un tableau et parce que c'est une méthode de bas niveau.Il est cependant juste un poil trop gros pour être souligné par l'optimiseur JIT. Il ne peut pas optimiser l'instruction de retour dont vous avez besoin pour renvoyer la référence de liste.

L'écriture de lst.Add (obj) dans votre code est gratuite, la première référence est disponible dans un registre CPU.

Une version de Add() qui renvoie la référence rend le code presque 5% plus lent. C'est beaucoup pire pour la méthode d'extension proposée, il y a tout un cadre de pile supplémentaire impliqué.

+0

Je suis sûr que vous n'apprécierez pas ce commentaire, mais peut-être pourriez-vous expliquer pourquoi je n'apprécierais pas cette réponse. C'était bien écrit, facile à comprendre et soulevé quelques bons points sur l'optimisation. –

Questions connexes