webentwicklung-frage-antwort-db.com.de

Ist die Verwendung von .NET 4.0-Beispielen in meinem C # -Code eine schlechte Designentscheidung?

Mit der Hinzufügung der Klasse Tuple in .net 4 habe ich versucht zu entscheiden, ob die Verwendung in meinem Design eine schlechte Wahl ist oder nicht. So wie ich es sehe, kann ein Tuple eine Abkürzung zum Schreiben einer Ergebnisklasse sein (ich bin sicher, dass es auch andere Verwendungen gibt). 

Also das:

public class ResultType
{
    public string StringValue { get; set; }
    public int IntValue { get; set; }
}

public ResultType GetAClassedValue()
{
    //..Do Some Stuff
    ResultType result = new ResultType { StringValue = "A String", IntValue = 2 };
    return result;
}

Ist gleichbedeutend mit:

public Tuple<string, int> GetATupledValue()
{
    //...Do Some stuff
    Tuple<string, int> result = new Tuple<string, int>("A String", 2);
    return result;
}

Abgesehen von der Möglichkeit, dass mir der Punkt von Tuples fehlt, ist das Beispiel mit einem Tuple eine schlechte Designwahl? Für mich scheint es weniger unordentlich zu sein, aber nicht als selbstdokumentierend und sauber. Das heißt, mit dem Typ ResultType ist es später sehr klar, was jeder Teil der Klasse bedeutet, Sie müssen jedoch zusätzlichen Code beibehalten. Mit Tuple<string, int> müssen Sie nachsehen und herausfinden, was jede Item darstellt, aber Sie schreiben und pflegen weniger Code.

Jede Erfahrung, die Sie mit dieser Wahl gemacht haben, wird sehr geschätzt.

164
Jason Webb

Tupel sind großartig, wenn Sie sowohl das Erstellen als auch das Verwenden von ihnen steuern.

Bei einer öffentlichen API sind sie jedoch weniger effektiv. Der Verbraucher (nicht Sie) muss die Dokumentation erraten oder nachschlagen, insbesondere für Dinge wie Tuple<int, int>.

Ich würde sie für private/interne Mitglieder verwenden, aber Ergebnisklassen für öffentliche/geschützte Mitglieder.

Diese Antwort hat auch einige Informationen.

160
Bryan Watts

So wie ich es sehe, ist ein Tuple eine Abkürzung zum Schreiben einer Ergebnisklasse (ich bin sicher, dass es auch andere Verwendungen gibt).

Es gibt in der Tat andere wertvolle Verwendungen für Tuple<>- die meisten von ihnen beinhalten die Abstraktion der Semantik einer bestimmten Gruppe von Typen, die eine ähnliche Struktur haben, und sie einfach als geordnete Menge von Werten behandeln. In allen Fällen besteht der Vorteil von Tupeln darin, dass der Namespace nicht durch reine Datenklassen beeinträchtigt wird, die Eigenschaften, aber keine Methoden bereitstellen.

Hier ist ein Beispiel für eine sinnvolle Verwendung von Tuple<>:

var opponents = new Tuple<Player,Player>( playerBob, playerSam );

Im obigen Beispiel möchten wir ein Paar von Gegnern darstellen. Ein Tuple ist eine bequeme Art, diese Instanzen zu paaren, ohne eine neue Klasse erstellen zu müssen. Hier ist ein anderes Beispiel:

var pokerHand = Tuple.Create( card1, card2, card3, card4, card5 );

Eine Pokerhand kann als nur ein Satz von Karten betrachtet werden - und Tuple (möglicherweise) ist eine vernünftige Möglichkeit, dieses Konzept auszudrücken.

beiseite stellen die Möglichkeit, dass ich Ich vermisse den Punkt von Tupel, ist der Beispiel mit einem Tuple ein schlechtes Design Wahl?

Die Rückgabe stark typisierter Tuple<>-Instanzen als Teil einer öffentlichen API für einen öffentlichen Typ ist selten eine gute Idee. Wie Sie selbst wissen, erfordert Tupel von den beteiligten Parteien (Bibliotheksautor, Bibliotheksbenutzer), dass sie sich vorab mit dem Zweck und der Interpretation der verwendeten Tuple-Typen einverstanden erklären. Es ist schwierig genug, APIs zu erstellen, die intuitiv und übersichtlich sind. Die Verwendung von Tuple<> verdeckt nur die Absicht und das Verhalten der API.

Anonyme Typen sind auch eine Art Tuple - sie sind jedoch stark typisiert und ermöglichen es Ihnen, eindeutige, aussagekräftige Namen für die zum Typ gehörenden Eigenschaften anzugeben. Anonyme Typen sind jedoch schwer für verschiedene Methoden zu verwenden. Sie wurden hauptsächlich zur Unterstützung von Technologien wie LINQ hinzugefügt, bei denen Projektionen Typen erzeugen, denen wir normalerweise keine Namen zuweisen möchten. (Ja, ich weiß, dass anonyme Typen mit denselben Typen und benannten Eigenschaften vom Compiler konsolidiert werden).

Meine Faustregel lautet: wenn Sie es von Ihrer öffentlichen Schnittstelle zurückgeben - machen Sie einen benannten Typ

Meine andere Faustregel für die Verwendung von Tupeln lautet: name-Methodenargumente und localc-Variablen vom Typ Tuple<> so klar wie möglich - lassen Sie den Namen die Bedeutung der Beziehungen zwischen Elementen des Tuples darstellen. Denken Sie an mein var opponents = ...-Beispiel.

Hier ist ein Beispiel eines Falles aus der realen Welt, in dem ich Tuple<> verwendet habe, um zu vermeiden, dass nur ein Datentyp für die Verwendung in meiner eigenen Assembly deklariert wird. Die Situation beinhaltet die Tatsache, dass es bei der Verwendung von generischen Wörterbüchern mit anonymen Typen schwierig wird, die TryGetValue()-Methode zum Suchen von Elementen im Wörterbuch zu verwenden, da für die Methode ein out-Parameter erforderlich ist, der nicht benannt werden kann:

public static class DictionaryExt 
{
    // helper method that allows compiler to provide type inference
    // when attempting to locate optionally existent items in a dictionary
    public static Tuple<TValue,bool> Find<TKey,TValue>( 
        this IDictionary<TKey,TValue> dict, TKey keyToFind ) 
    {
        TValue foundValue = default(TValue);
        bool wasFound = dict.TryGetValue( keyToFind, out foundValue );
        return Tuple.Create( foundValue, wasFound );
    }
}

public class Program
{
    public static void Main()
    {
        var people = new[] { new { LastName = "Smith", FirstName = "Joe" },
                             new { LastName = "Sanders", FirstName = "Bob" } };

        var peopleDict = people.ToDictionary( d => d.LastName );

        // ??? foundItem <= what type would you put here?
        // peopleDict.TryGetValue( "Smith", out ??? );

        // so instead, we use our Find() extension:
        var result = peopleDict.Find( "Smith" );
        if( result.First )
        {
            Console.WriteLine( result.Second );
        }
    }
}

P.S. Es gibt eine andere (einfachere) Möglichkeit, die Probleme zu umgehen, die sich aus anonymen Typen in Wörterbüchern ergeben, und zwar mit dem Schlüsselwort var, damit der Compiler den Typ für Sie "herleiten" kann. Hier ist diese Version:

var foundItem = peopleDict.FirstOrDefault().Value;
if( peopleDict.TryGetValue( "Smith", out foundItem ) )
{
   // use foundItem...
}
78
LBushkin

Tupel können nützlich sein ... aber sie können später auch schmerzhaft sein. Wenn Sie eine Methode haben, die Tuple<int,string,string,int> zurückgibt, wissen Sie dann, was diese Werte später sind. Waren sie ID, FirstName, LastName, Age oder waren sie UnitNumber, Street, City, ZipCode.

17
Matthew Whited

Tuples sind aus Sicht eines C # -Programmierers eine ziemlich unterdrückende Ergänzung der CLR. Wenn Sie über eine Sammlung von Elementen verfügen, deren Länge variiert, benötigen Sie zum Zeitpunkt der Kompilierung keine eindeutigen statischen Namen.

Wenn Sie jedoch eine Sammlung mit konstanter Länge haben, bedeutet dies, dass die festen Positionen in der Sammlung jeweils eine bestimmte vordefinierte Bedeutung haben. Und es ist immer es ist besser, ihnen in diesem Fall geeignete statische Namen zu geben, anstatt sich an die Bedeutung von Item1, Item2 usw. zu erinnern.

Anonyme Klassen in C # bieten bereits eine hervorragende Lösung für die häufigste private Verwendung von Tupeln. Sie geben den Elementen aussagekräftige Namen, so dass sie tatsächlich in diesem Sinne überlegen sind. Das einzige Problem ist, dass die benannten Methoden nicht auslaufen können. Ich würde es vorziehen, diese Einschränkung aufzuheben (vielleicht nur für private Methoden), als eine spezifische Unterstützung für Tupel in C # zu haben:

private var GetDesserts()
{
    return _icecreams.Select(
        i => new { icecream = i, topping = new Topping(i) }
    );
}

public void Eat()
{
    foreach (var dessert in GetDesserts())
    {
        dessert.icecream.AddTopping(dessert.topping);
        dessert.Eat();
    }
}
9

Ähnlich wie das Schlüsselwort var ist es als Bequemlichkeit gedacht - wird aber ebenso leicht missbraucht.

Meiner bescheidenen Meinung nach, Tuple nicht als Rückgabe-Klasse aussetzen. Verwenden Sie es privat, wenn die Datenstruktur eines Dienstes oder einer Komponente dies erfordert, geben Sie jedoch wohlgeformte bekannte Klassen aus öffentlichen Methoden zurück.

// one possible use of Tuple within a private context. would never
// return an opaque non-descript instance as a result, but useful
// when scope is known [ie private] and implementation intimacy is
// expected
public class WorkflowHost
{
    // a map of uri's to a workflow service definition 
    // and workflow service instance. By convention, first
    // element of Tuple is definition, second element is
    // instance
    private Dictionary<Uri, Tuple<WorkflowService, WorkflowServiceHost>> _map = 
        new Dictionary<Uri, Tuple<WorkflowService, WorkflowServiceHost>> ();
}
8
johnny g

Wie wäre es, wenn Sie Tuples in einem Dekor-Sortier-Undekorat-Muster verwenden? (Schwartzian Transform für die Perl-Leute). Hier ist ein erfundenes Beispiel, um sicher zu sein, aber Tuples scheinen ein guter Weg zu sein, um mit so etwas umzugehen:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] files = Directory.GetFiles("C:\\Windows")
                    .Select(x => new Tuple<string, string>(x, FirstLine(x)))
                    .OrderBy(x => x.Item2)
                    .Select(x => x.Item1).ToArray();
        }
        static string FirstLine(string path)
        {
            using (TextReader tr = new StreamReader(
                        File.Open(path, FileMode.Open)))
            {
                return tr.ReadLine();
            }
        }
    }
}

Nun hätte ich ein Object [] aus zwei Elementen oder in diesem speziellen Beispiel eine Zeichenfolge [] aus zwei Elementen verwenden können. Der Punkt ist, dass ich etwas als zweites Element in einem Tuple hätte verwenden können, das intern verwendet wird und ziemlich einfach zu lesen ist.

5
Clinton Pierce

Die Verwendung einer Klasse wie ResultType ist klarer. Sie können den Feldern in der Klasse aussagekräftige Namen geben (während sie bei einem Tuple Item1 und Item2 heißen würden). Dies ist umso wichtiger, wenn die Typen der beiden Felder gleich sind: Der Name unterscheidet eindeutig zwischen ihnen.

5
Richard Fearn

IMO diese "Tupel" sind grundsätzlich alle anonymen struct-Typen mit öffentlichem Zugriff mit unbenannten Mitgliedern.

Der einzigeOrt, den ich Tuple verwenden würde, ist, wenn Sie einige Daten in einem sehr begrenzten Umfang schnell zusammenblocken müssen. Die Semantik der Daten sollte offensichtlich sein, sodass der Code nicht schwer zu lesen ist. Die Verwendung eines Tupels (int, int) für (Zeile, Spalte) erscheint daher sinnvoll. Aber es ist schwer, einen Vorteil gegenüber einer struct mit benannten Mitgliedern zu finden (damit keine Fehler gemacht werden und Zeile/Spalte nicht versehentlich ausgetauscht werden).

Wenn Sie Daten an den Anrufer zurücksenden oder Daten von einem Anrufer akzeptieren, sollten Sie wirklich eine struct mit benannten Mitgliedern verwenden.

Nehmen Sie ein einfaches Beispiel:

struct Color{ float r,g,b,a ; }
public void setColor( Color color )
{
}

Die Tuple-Version

public void setColor( Tuple<float,float,float,float> color )
{
  // why?
}

Ich sehe keinen Vorteil darin, Tuple anstelle einer Struktur mit benannten Mitgliedern zu verwenden. Die Verwendung unbenannter Member ist ein Rückschritt für die Lesbarkeit und Verständlichkeit Ihres Codes.

Tuple scheint mir eine faule Art zu sein, um zu vermeiden, dass struct mit tatsächlich benannten Mitgliedern erstellt wird. Überbeanspruchung von Tuple, wenn Sie wirklich der Meinung sind, dass Sie/oder jemand anderes, dem Sie mit Ihrem Code begegnen, Mitglieder braucht, ist ein schlechtes Ding, wenn ich einen gesehen hätte.

4
bobobobo

Verurteilen Sie mich nicht, ich bin kein Experte, aber mit neuen Tuples in C # 7.x könnten Sie etwas zurückgeben:

return (string Name1, int Name2)

Zumindest können Sie es jetzt benennen, und die Entwickler sehen möglicherweise Informationen.

2
Denis Gordin

Das hängt natürlich davon ab! Wie Sie gesagt haben, kann ein Tupel Code und Zeit sparen, wenn Sie einige Elemente für den lokalen Verbrauch zusammenfassen möchten. Sie können sie auch verwenden, um generischere Verarbeitungsalgorithmen zu erstellen, als wenn Sie eine konkrete Klasse übergeben. Ich kann mich nicht erinnern, wie oft ich mir gewünscht habe, etwas anderes als KeyValuePair oder DataRow zu haben, um schnell ein Datum von einer Methode zur anderen zu übergeben. 

Auf der anderen Seite ist es durchaus möglich, es zu übertreiben und Tupel zu umgehen, in denen nur erraten werden kann, was sie enthalten. Wenn Sie ein Tuple für mehrere Klassen verwenden, ist es vielleicht besser, eine konkrete Klasse zu erstellen.

Bei der Moderation können Tupel natürlich zu prägnanterem und lesbarerem Code führen. Beispiele für die Verwendung von Tuples in anderen Sprachen finden Sie in C++, STL und Boost. Am Ende müssen wir jedoch alle testen, wie sie in die .NET-Umgebung passen.

2

Tupel sind ein nutzloses Framework-Feature in .NET 4. Ich denke, eine großartige Gelegenheit wurde mit C # 4.0 verpasst. Ich hätte gerne Tupel mit namentlich benannten Mitgliedern gehabt, so dass Sie auf die verschiedenen Felder eines Tuples anstelle von Value1 , Value2 usw. zugreifen könnten.

Es hätte eine Änderung der Sprache (Syntax) erforderlich gemacht, wäre aber sehr nützlich gewesen.

1

Ich würde persönlich nie einen Tuple als Rückgabetyp verwenden, da es keinen Hinweis darauf gibt, was die Werte darstellen. Tupel haben einige nützliche Anwendungen, da sie im Gegensatz zu Objekten Werttypen sind und daher Gleichheit verstehen. Aus diesem Grund werde ich sie als Wörterbuchschlüssel verwenden, wenn ich einen mehrteiligen Schlüssel benötige, oder als Schlüssel für eine GroupBy-Klausel, wenn ich nach mehreren Variablen gruppieren möchte und keine verschachtelten Gruppierungen (Wer möchte schon verschachtelte Gruppierungen?). Um das Problem mit extremer Ausführlichkeit zu überwinden, können Sie sie mit einer Hilfsmethode erstellen. Wenn Sie häufig auf Mitglieder zugreifen (über Item1, Item2 usw.), sollten Sie wahrscheinlich ein anderes Konstrukt wie eine Struktur oder eine anonyme Klasse verwenden.

1
Seth Paulson

Ich habe in verschiedenen Szenarien Tupel verwendet, sowohl die Tuple als auch die neue ValueTuple, und bin zu folgendem Ergebnis gekommen: nicht verwenden .

Jedes Mal bin ich auf folgende Probleme gestoßen:

  • code wurde aufgrund fehlender Namensgebung unlesbar;
  • klassenhierarchie-Features wie Basis-DTO und Child-Class-DTOs können nicht verwendet werden.
  • wenn sie an mehreren Stellen verwendet werden, werden diese hässlichen Definitionen anstelle eines reinen Klassennamens kopiert und eingefügt.

Meiner Meinung nach sind Tupel ein Nachteil von C # und nicht ein Merkmal.

Ich habe etwas ähnliche, aber viel weniger scharfe Kritik an Func<> und Action<>. Dies ist in vielen Situationen hilfreich, vor allem in den einfachen Varianten Action und Func<type>. Darüber hinaus habe ich festgestellt, dass das Erstellen eines Delegattyps eleganter, lesbarer und wartbarer ist und Ihnen mehr Funktionen bietet, wie ref/out-Parameter.

0
Mr. TA