2015-07-22 9 views
6

Ich komme aus dem Wild Wild West von PHP und Javascript, wo Sie alles aus einer Funktion zurückgeben können. Während ich diesen Mangel an Verantwortlichkeit hasse, stehe ich auch vor einer neuen Herausforderung, meinen Code "perfekt" zu halten.Vermeiden von Bereichsüberschreitungen in allgemeinen Funktionen

Ich habe diese generische Funktion ein zufälliges Element aus einer Liste will

public static T PickRandom<T>(this IList<T> list) { 
    Random random = new Random(); 
    int rnd = random.Next(list.Count); 
    return list[rnd]; 
} 

Aber ich holen mich aus mit mit 0 Werten auf eine Liste zu schützen. Natürlich kann ich von dieser Funktion außer T nichts zurückgeben, wie zum Beispiel false oder -1. Ich kann natürlich tun dies

if(myList.Count > 0) 
    foo = Utilites.PickRandom(myList); 

Allerdings gibt es so viele verrückte Dinge in C#, die ich weiß nicht, über, und für diese App Ich schaffe ich sehr, sehr oft ein Zufallselement aus einem wählen Liste, die in ihrer Zählung ständig dekrementiert werden könnte. Gibt es einen besseren Weg?

+4

Was passieren soll, wenn einige dieser Methode auf eine leere Liste nicht nennen? – Rob

+0

Ich denke, es sollte nichts zurückgeben, aber das ist nicht möglich? – user3822370

+1

Wenn die Liste Referenztypen (z. B. Objekte) enthält, können Sie das tun. Aber das funktioniert nicht für Dinge wie "int", außer du sagst explizit, dass es null-fähig ist. Es scheint mir, dass es ** ungültig ist, diese Methode auf einer leeren Liste aufzurufen, daher sollte eine Ausnahme in Ordnung sein. Die Rückgabe von null könnte für den Aufrufer unerwartet sein. Ich würde eine Ausnahme auslösen, wenn Count == 0 in der PickRandom-Methode. Außerdem sollten Sie folgendes verwenden: 'random.Next (list.Count - 1)', oder Sie können eine Ausnahme erhalten, wenn Sie versuchen, auf das Element 'last + 1' zuzugreifen. – Rob

Antwort

8

Die Optionen sind

haben
return default(T) 

, die eine zweideutige Verhalten sein, als dass ein gültiges Element der Liste sein könnte.

Oder Sie können etwas wie -1 zurückgeben, wie Sie gesagt haben, aber das ist ziemlich mit Ihrem Code gekoppelt.

Oder Sie können null zurückgeben, aber das kann nur getan werden, wenn T natürlich ein NULL-fähiger Typ ist.

In allen vorherigen Fällen, wenn der Aufrufer diese Situation nicht kennt, kann die Anwendung mit einem ungültigen Wert fortgesetzt werden, was zu unbekannten Konsequenzen führt.

So wahrscheinlich die beste Option ist eine Ausnahme zu werfen:

throw new InvalidOperationException(); 

Mit diesem Ansatz Sie schnell fehlschlagen und Sie sicher, dass nichts unerwartetes geschieht Absichten jenseits des Anrufers machen.

Ein Grund mehr, diese Option zu sichern. Nehmen Sie zum Beispiel die Erweiterungsmethoden von Linq. Wenn Sie First(), Single() oder Last() auf einer leeren Liste aufrufen, erhalten Sie eine InvalidOperationException mit der Nachricht "Sequenz enthält keine Elemente". Es ist immer eine gute Sache, Ihrer Klasse ein Verhalten zu geben, das dem der Framework-Klassen ähnlich ist.


Ich füge eine Randnotiz dank Alexei Levenkov Kommentar in der Frage hinzu. Diese zufällige Generation ist nicht der beste Ansatz. Werfen Sie einen Blick auf this question.


Zweite Randnotiz. Sie deklarieren Ihre Funktion als eine Erweiterungsmethode für IList<T> (Sie tun das, indem Sie den this vor dem ersten Parameter verwenden) aber Sie nennen es dann genau wie eine statische Hilfsmethode.Eine Erweiterung Methode ist ein syntaktischer Zucker, der, statt dies zu tun:

foo = Utilites.PickRandom(myList); 

können Sie dies tun:

foo = myList.PickRandom(); 

Weiteren Informationen über Erweiterungsmethoden here gefunden werden können.

+4

Ich würde sagen, das ist die richtige Antwort.Ich würde mir jedoch wünschen, dass Sie die Probleme mit der Wiedergewinnung eines bestimmten Wertes betonen. Nämlich die Tatsache, dass ein Anrufer mit ziemlicher Sicherheit vergisst, nach dem Wert zu suchen. Daher ist es wahrscheinlich der beste Zug (den Sie bereits angegeben haben), dem Indexer zu erlauben, 'IndexOutOfRangeException' zu werfen. –

+1

Danke für diese Antwort. Jetzt habe ich nur Probleme zu sehen, wie der Anrufer das handhabt. Das PickRandom löst eine Ausnahme aus, ich werde es irgendwie handhaben, aber was macht der Aufrufer? Woher weiß es, um Kaution? Braucht es einen eigenen Versuch/Fang? – user3822370

+1

Ja, der Anrufer muss sich dieser Situation bewusst sein, Sie (als Erweiterungsmethode) sind nicht dafür verantwortlich zu entscheiden, was in diesem Fall zu tun ist. Also sollte er entweder vor dem Anruf nach der Liste suchen oder einen Versuch machen. Angesichts dieser Situation ist der erste Ansatz besser, da eine leere Liste etwas ist, was Sie erwarten können und nicht etwas, das nicht vorhersehbar ist (dort sollten Sie Try-Catchs verwenden). – Andrew

0

Eine andere Alternative ist das folgende Paar Überladungen statt das Original. Damit sollte dem Anrufer klar sein, dass er einen voreingestellten Zufallswert liefern soll, wenn er nicht aus der Liste "abgeholt" werden kann.

public static T PickRandomOrReturnDefault<T>(this IList<T> list, T defaultRandomValue) 
{ 
    if (list == null || list.Count == 0) return defaultRandomValue; 

    Random random = new Random(); 
    int rnd = random.Next(list.Count); 
    return list[rnd]; 
} 

public static T PickRandomOrReturnDefault<T>(this IList<T> list, Func<T> createRandomValue) 
{ 
    if (list == null || list.Count == 0) return createRandomValue(); 

    Random random = new Random(); 
    int rnd = random.Next(list.Count); 
    return list[rnd]; 
} 

Hinweis: Sie sollten wahrscheinlich erwägen, zufällig ein statisches Element Feld der Klasse, anstatt sie immer und immer wieder zu instanziieren. Siehe die Antwort für diesen Beitrag Correct method of a "static" Random.Next in C#?

0

Eine andere Option, die Sie haben, ist die Verwendung der Maybe<T> Monade. Es ist sehr ähnlich wie Nullable<T>, aber arbeitet mit Referenztypen.

public class Maybe<T> 
{ 
    public readonly static Maybe<T> Nothing = new Maybe<T>(); 

    public T Value { get; private set; } 
    public bool HasValue { get; private set; } 

    public Maybe() 
    { 
     HasValue = false; 
    } 

    public Maybe(T value) 
    { 
     Value = value; 
     HasValue = true; 
    } 

    public static implicit operator Maybe<T>(T v) 
    { 
     return v.ToMaybe(); 
    } 
} 

Ihr Code könnte dann wie folgt aussehen:

private static Random random = new Random(); 
public static Maybe<T> PickRandom<T>(this IList<T> list) 
{ 
    var result = Maybe<T>.Nothing; 
    if (list.Any()) 
    { 
     result = list[random.Next(list.Count)].ToMaybe(); 
    } 
    return result; 
} 

Sie würden es gerne nutzen:

var item = list.PickRandom(); 

if (item.HasValue) { ... } 

persönlich nenne ich meine vielleicht Methoden mit am Ende Vielleicht.

Es wäre wie folgt aus:

var itemMaybe = list.PickRandomMaybe(); 

if (itemMaybe.HasValue) { ... } 
Verwandte Themen