Laden...

Enum-Alternative im String ausgeben

Erstellt von OXO vor 3 Jahren Letzter Beitrag vor 3 Jahren 814 Views
O
OXO Themenstarter:in
86 Beiträge seit 2020
vor 3 Jahren
Enum-Alternative im String ausgeben

Hallo zusammen,

in einem enum gibt es verschiedene Werte in der Art:


enum Versions
{
    V1,
    V2,
    V3,
    Latest = V3,
}

In einem Test wird aus einer Master-Datei die aktuellste Version ermittelt.
Stellt sich raus, dass es bereits eine "V4" gibt, aber im enum "Latest" immer noch die "V3" eingestellt ist, dann wird der Test rot.

Im Assert des Tests wird die ausgelesene "V4" natürlich gegen "Lastest" geprüft.
Stimmen die beiden Versionen nicht überein, gibt NUnit folgendes aus:

Expected: V4
But was: Latest

Ich würde das aber gerne anders ausgeben und dort die Enum-Alternative als String haben:

Expected: V4
But was: V3

Wie könnte ich das hinbekommen?

T
2.222 Beiträge seit 2008
vor 3 Jahren

Im einfachsten Fall musst du nur schauen, welche Einträge das Enum liefert mit gleichen Wert aber anderem Namen.
Kannst du vermutlich in einer Methode per Linq lösen.
Dafür muss die nur über Enum.GetValues dir alle Werte holen und alle mit gleichen Wert aber unterschiedlichen Namen ermitteln.
Hast du zwei Treffer, musst du nur den anderen nehmen.

Nachtrag:
Was mich aber interessieren würde, wozu es ein Versions Enum gibt?
Was wird damit den Versioniert?

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

16.827 Beiträge seit 2008
vor 3 Jahren

Über ein Enum allein gar nicht, weil ein Enum im Endeffekt in dieser Form nur Int-Werte repräsentiert, die dann eben verglichen werden.

Ich persönlich hab noch nie eine Enum-basierte Versionierung gesehen, die funktioniert.
Wenn ich das bei Kunden sehe, dann empfehle ich ihnen meist das zu lassen.
Versionen stellen immer Inhalte dar - und ein Enum alleine kann das eigentlich nicht.

4.938 Beiträge seit 2008
vor 3 Jahren

Sehe ich genauso.

In deinem Code, OXO, wäre als interne Repräsentation bisher


enum Versions
{
    V1 = 0,
    V2 = 1,
    V3 = 2,
    Latest = 2,
}

und das wäre sehr irritierend bei Zahlenvergleichen (oder auch Serialisierung).
Also wenigstens


enum Versions
{
    V1 = 1,
    V2,
    V3,
    Latest = V3,
}

Warum benutzt du nicht einfach nur einen int (wenn es sowieso nur eine Major-Version gibt) oder die Version-Klasse (falls es auch Minor-Versionen etc. gibt)?

Willst du mit dem Test überprüfen, ob dein Code noch nicht die neueste Version unterstützt (weil du dann noch Code dafür entwickeln mußt)?
Du könntest dann aus Latest auch eine Konstante machen:


const Version LatestVersion = Version.V3; // s. mein PS

und dessen Wert dann testen.

PS: Ein Enum, welches nicht mit dem [Flags]-Attribut versehen ist, sollte immer in der Einzahl benannt werden, bei dir hier also enum Version (da ja nur genau 1 Wert repräsentiert wird).

16.827 Beiträge seit 2008
vor 3 Jahren

Das Problem was OXO hat ist aber, dass der Compiler bei ihm das Wort Latest nicht interpretiet (weil er es nicht kann).
OXOs Wunsch wäre ja, dass - wenn V4 hinzu kommt, das Latest automatisch eben V4 ist und nicht mehr V3.

enum Versions
{
    V1 = 1,
    V2,
    V3,
    Latest = V3,
    V4 // << V4 sollte nun Latest sein.
}

Natürlich kann das der Compiler nicht.
Da hilft im Endeffekt die Konstante auch nicht, weil ja auch diese noch auf den älteren Eintrag zeigt.

Die einzige Möglichkeit, die ich hier sehe ist - und das geht eben nicht mit dem Enum alleine - die höchste Int-Value des Enums finden und prüfen, ob "Latest" diesem entspricht.
Aber das Konstrukt bleibt halt trotzdem fehleranfällig.

4.938 Beiträge seit 2008
vor 3 Jahren

So wie ich aber dies verstehe, gibt es V4 noch gar nicht im enum Versions, sondern nur in einer externen Master-Datei (was auch immer diese beinhaltet).

Mal schauen, was OXO dazu weiter schreibt.

O
OXO Themenstarter:in
86 Beiträge seit 2020
vor 3 Jahren

Also Abt hatte Recht mit dem was er schreibt.
Die Versionen sind mit bestimmter Hardware verknüpft, die an anderer Stelle über passende Dateien eingepflegt und gewartet werden (das auch noch in verschiedenen Formaten, aber gut). Eine neuere Version wird also erst einmal als Master dort festgestellt. Intern kann man verschiedene nutzen, aber wie man sieht, muss das Enum über diesen Mechanismus dann immer nachgepflegt werden.

Das was ich mit Versions geschrieben hatte, war nur das Grundprinzip, um ein einfaches Beispiel für hier zu haben.

Auch die Kommentare, dass das ganze System Fehleranfällig ist, sehe ich genauso. Aktuell fällt mir nur nicht ein, wie ich sonst aus der Misere kommen könnte, wenn mehrere Stellen beteiligt sind und jeder Seins macht.

Würde sowas wie hier verlässlich funktionieren?
Also sprich, such mir alle Werte, die dem der latestVersion entsprechen und da es bei den Enums ja nur 2 sein können, nimm einfach das letzte aus der Liste.
Oder wird das zufällig ausgegeben dann?


private Versions GetLatestVersionAssigned(Versions latestVersion)
{
    var latestVersionAssigned = Enum.GetValues(typeof(Versions)).Cast<Versions>().Where(version => version == latestVersion).Last();
    return latestVersionAssigned;
}

2.079 Beiträge seit 2012
vor 3 Jahren

Der Code sucht eine per Parameter angegebene Version und gibt sie zurück.
Und wenn er sie nicht findet, fliegt eine Exception.
Wenn Du nicht an der Exception hängst, geht das auch einfacher:

private Versions GetLatestVersionAssigned(Versions latestVersion)
{
    return latestVersion;
}

Ich glaube nicht, dass das dein Plan war 😄

So wie ich dich verstanden habe, brauchst Du anstelle des "Where" ein "OrderBy", was nach dem Enum-Wert sortiert. Und der Parameter fliegt dann auch raus.
So hättest Du immer die höchste Version.

Und wenn Du anhand von "V4" das "Latest" oder umgekehrt suchst, musst Du wohl per String-Operationen die Namen vergleichen. Von der Reihenfolge der Enum-Werte würde ich mich jedenfalls nicht abhängig machen.

O
OXO Themenstarter:in
86 Beiträge seit 2020
vor 3 Jahren

Eigentlich brauche ich alle Enum-Werte mit demselben int-Wert, wie auch immer ich das per Link sagen muss, und dann eben nicht den, der denselben Namen hat, wie der Name des vorgegebenen Enum-Werts "Latest"

Schaue ich in den Debugger, zeigt der mir bei allem, auch bei GetValues mit OrderBy immer 2x Latest an, was ich schon seltsam finde, ehrlich gesagt.

4.938 Beiträge seit 2008
vor 3 Jahren

Du möchtest doch eigentlich nicht den Enum-Wert, sondern den Enum-Namen:


var names = Enum.GetNames<Version>(); // bzw. Enum.GetNames(typeof(Version))

Und darüber dann iterieren, und per (Try)Parse den Wert ermitteln und dann vergleichen (wenn der Name != "Latest" ist), s.a. Code-Beispiel unter "Hinweise" in Enum.GetNames.

O
OXO Themenstarter:in
86 Beiträge seit 2020
vor 3 Jahren

Also das ist schon ein witziges Spielchen und ich sitze auf dem Schlauch.
Wenn ich das so versuche, sehe ich im Debugger zunächst verschiedene Namen in dem Array aus GetNames(..).

In einer foreach-Schleife auch noch bei der "names"-Auflistung. Kommt es aber dann zu der Zuweisung zur Variablen "name" in der foreach, dann steht da plötzlich 2x "Latest" drin


var names = Enum.GetNames(typeof(Versions));
foreach (var name in names)
{
    var parsedValue = (Versions)Enum.Parse(typeof(Versions), name);   // Cast zwar unnötig, aber zur Sicherheit auch ausprobiert
    if (parsedValue == latestVersion && !name.Equals(latestVersion.ToString()))
        return parsedValue;
}

4.938 Beiträge seit 2008
vor 3 Jahren

Du sollst explizit auf "Latest" vergleichen, nicht auf latestVersion.ToString() (was keinen Sinn macht, da du einen Namen mit einem Wert vergleichst!).

Im Debugger siehst du den Aufruf von Enum.GetName (s. bes. unter "Hinweise")!
Bei mehreren Enum-Namen mit gleichem Wert (Value) wird also irgendeiner dieser Namen ausgegeben!

O
OXO Themenstarter:in
86 Beiträge seit 2020
vor 3 Jahren

Das hatte ich auch schon. Aber hilft nicht so viel, aus 2 Gründen, zum einen, könnte ja jemand auf die Idee kommen und das ganze Flag umbenennen, dann würde der String-Vergleich auch nicht mehr gehen. Zum anderen, löst sich das nicht in das richtige Flag des Enums auf, obwohl die Namen anders sind, vergleiche sind ja nur die Namen.

Im Debugger siehst du den Aufruf von
>
(s. bes. unter "Hinweise")!
Bei mehreren Enum-Namen mit gleichem Wert (Value) wird also irgendeiner dieser Namen ausgegeben!

Hhm, dann kann es wohl gar nicht gehen, oder bleibt ein Zufallsprodukt, wie Du schreibst 😦

Siehe hier:

4.938 Beiträge seit 2008
vor 3 Jahren

Sehe ich jetzt erst: du sollst dann natürlich name zurückgeben (du willst doch den Enum-Namen als String haben)!

O
OXO Themenstarter:in
86 Beiträge seit 2020
vor 3 Jahren

Nicht unbedingt als String. Der Enum-Wert mit dem richtigen Text, wäre mir lieber, daher auch ein Typ des Enums als return-Value

2.079 Beiträge seit 2012
vor 3 Jahren

Was Du vor hast, funktioniert nicht - zumindest wenn ich es richtig verstanden habe.

Führ mal folgendes aus:

public enum Test
{
    A = 1,
    B = 1,
}
public class Program
{
    public static void Main(string[] args)
    {
        Console.WriteLine(Test.A == Test.B);
        Console.WriteLine(Test.B == Test.A);
    }
}

Ausgabe:

True
True

Du kannst also so lange die Enum-Werte durchsuchen, wie Du willst, Du wirst immer beide Einträge finden und immer noch nicht wissen, wer nun "Latest" ist.

Nochmal:

Ein Enum ist ein Int (bzw. das, was man einstellt), die Namen sind bloß eine Unterstützung drüber hinaus.
Wenn Du wissen willst, welche Version die Höchste ist, brauchst Du eine Methode, die genau diese Version raus sucht, im Enum hat die nichts verloren.

Oder Du arbeitest auf Klassenbasis mit statischen Properties, die Instanzen dahinter bieten dann mehr Möglichkeiten.

16.827 Beiträge seit 2008
vor 3 Jahren

Vergiss diese String-Frickelei aka Magic string. Das ist eine der schlimmsten Dinge, die man in der Software Entwicklung machen kann.

Wenn Du das Enum Vorgehen unbedingt willst (ich versteh nicht wieso), dann lass das Latest aus dem Enum raus und ermittle es extern.

enum Version
{
    V1 = 1,
    V2 = 2,
    V3 = 3,
    V4 = 4
}

Version latest = Enum.GetValues(typeof(Version)).Cast<Version>().Max();

4.938 Beiträge seit 2008
vor 3 Jahren

@Abt: Statt Last() sollte besser Max() verwendet werden (falls auch negative Wert dort eingetragen werden), denn die Werte werden nach der Binärrepräsentation sortiert, s. Enum.GetValues.

@Oxo: Du hast Enums (immer noch) nicht verstanden!

16.827 Beiträge seit 2008
vor 3 Jahren

Oh stimmt, hast natürlich recht.

O
OXO Themenstarter:in
86 Beiträge seit 2020
vor 3 Jahren

@Oxo: Du hast Enums (immer noch) nicht verstanden!

Wieso meinst Du? 😃
Finde schon, dass man bei den Enums auch zwischen Wert und dem eigentlichen Namen unterscheiden können muss - irgendwie zumindest (kopf durch die wand)

16.827 Beiträge seit 2008
vor 3 Jahren

Finde schon, dass man bei den Enums auch zwischen Wert und dem eigentlichen Namen unterscheiden können muss

Und genau das sollte man nicht tun, weil Enum-Namen unterm strich nur Entwickler-Hilfen darstellen.
Daher teile ich den Eindruck von Th69, dass Du Enums nicht verstanden hast.

4.938 Beiträge seit 2008
vor 3 Jahren

Noch als Nachtrag zu meinem "Latest"-Vergleich: hier sollte man dann wirklich stattdessen nameof(Version.Latest) verwenden, damit man einen Compilerfehler erhält, wenn der Enum-Name nicht existiert.
Aber ich bin ja auch dafür, daß Latest nicht als Enum-Member zu verwenden (hatte ich oben ja auch schon geschrieben).

2.079 Beiträge seit 2012
vor 3 Jahren

@OXO:

bau mal ein kleines Beispiel-Programm mit deinem Enum und rufe damit eine Methode auf.

Du wirst sehen:
Im Code landet nur der reine Zahlenwert und ein Decompiler versucht auf dem gleichen Weg wie Du herauszufinden, welcher Eintrag das war.
Wenn Du dir den CIL-Code anschaust, wird auch klar, warum: Es wird nur nur der reine Zahlenwert übergeben, das Enum findest Du eigentlich nur in den Metadaten der Methode wieder.

O
OXO Themenstarter:in
86 Beiträge seit 2020
vor 3 Jahren

Okay, überzeugt 😉

Bin trotzdem froh, dass wir diesen Thread hatten! Danke Euch allen, dass Ihr Euch dazugeschalten habt!