2009-03-10 2 views
1

Nehmen wir an, ich habe ein Datenobjekt, aber dieses Objekt kann einen von mehreren Arten von Daten enthalten.Der beste Weg, um ein Objekt vom Typ Multi zu entwerfen

class Foo 
{ 
    int intFoo; 
    double doubleFoo; 
    string stringFoo; 
} 

Jetzt will ich einen Accessor erstellen. Ein Weg, um an diese Daten zu kommen. Natürlich könnte ich mehrere Accessoren erstellen:

public int GetIntFoo(); 
public double GetDoubleFoo(); 
public string GetStringFoo(); 

Oder ich könnte mehrere Eigenschaften

public int IntFoo { get; set; } 
public double DoubleFoo { get; set; } 
public string StringFoo { get; set; } 

Ich mache nicht, dass dies ein sehr gutes Design. Es erfordert, dass der Client-Code sich mehr um den Typ kümmert, als er sein sollte. Außerdem benötige ich wirklich nur einen einzigen Wert für diese Klasse und das oben genannte würde erlauben, dass jeder Typ gleichzeitig zugewiesen wird. Nicht gut.

Eine Option ist die Verwendung von Generika.

class Foo<T> 
{ 
    public T TheFoo { get; set; } 
} 

Dies ist jedoch nicht eine Foo schafft, schafft es ein Foo <T>. Ein anderer Typ für jeden, so dass ich sie nicht wirklich als denselben Typ verwenden kann.

Ich könnte Foo <T> von FooBase ableiten, dann behandeln sie alle als FooBase, aber dann bin ich zurück in das Problem des Zugriffs auf die Daten.

Eine andere Generics Option ist, so etwas zu verwenden:

class Foo 
{ 
    string stringRepresentationOfFoo; 
    public T GetFoo<T>() { return /* code to convert string to type */ } 
} 

Natürlich ist das Problem ist, dass jede Art von T weitergegeben werden kann, und ehrlich gesagt, ist es ein bisschen beschäftigt.

Ich könnte auch nur die Werte boxen und ein Objekt zurückgeben, aber dann gibt es keine Art Sicherheit.

Idealerweise möchte ich alle Foo's gleich behandeln, aber ich möchte Sicherheit geben, damit ich, wenn es kein StringFoo gibt, nicht einmal einen Verweis auf einen StringFoo kompilieren kann.

Vielleicht ist das gar nicht möglich .. und ich muss Kompromisse machen, aber vielleicht fehlt mir etwas.

Irgendwelche Ideen?

EDIT:

Ok, so wie Daniel weist darauf hin, die Kompilierung einer Laufzeit-Typprüfung nicht praktikabel ist.

Was ist meine beste Option, um das zu tun, was ich hier machen möchte? Nämlich, Behandle alle Foo's gleich, aber immer noch einen relativ vernünftigen Zugriffsmechanismus?

EDIT2:

Ich will nicht den Wert auf verschiedene Arten konvertieren. Ich möchte den korrekten Typ für den Wert zurückgeben. Das heißt, wenn es ein Double ist, möchte ich kein int zurückgeben.

+0

Wenn Ihre Instanz von Foo ein beliebiger Wert sein kann, woher weiß Ihr Verbraucher dann, in welcher Art von Variable er das Ergebnis speichern soll? –

+0

Habe ich recht, wenn ich denke, dass Sie versuchen, sich in C# (einer stark typisierten Sprache) wie eine locker geschriebene Sprache zu verhalten? –

+0

Nein, Sie sind nicht korrekt. Ich versuche, stark zu tippen. Das ist der ganze Punkt, sonst würde ich nur Objekte und Cast verwenden. –

Antwort

4

Wie wäre es, die Variable als Parameter an die get übergeben? Wie folgt aus:

int i = foo.get(i); 

Dann in Ihrer Klasse, würde haben Sie so etwas wie:

public int get(int p) { 
    if(this.type != INTEGER) throw new RuntimeException("Data type mismatch"); 
    return this.intVal; 
} 

public float get(float p) { 
    if(this.type != FLOAT) throw new RuntimeException("Data type mismatch"); 
    return this.floatVal; 
} 

Diese Art der Art verwandelt sich die Überprüfung von innen nach außen: anstelle der Prüfung, welche Art foo hält, haben Sie foo überprüfen Sie, welchen Typ Sie wollen. Wenn es Ihnen diesen Typ geben kann, tut es das, oder es löst eine Laufzeitausnahme aus.

+0

Ich denke, dass dies die beste Lösung in diesem Fall sein kann. Ich würde lieber einen out-Parameter verwenden. –

3

Ich glaube nicht, dass dies funktionieren könnte (so dass Sie die Compiler-Fehler Sie möchten)

Was würden Sie dies tun wollen:

Foo bar = (new Random()).Next(2) == 0 ? new Foo("bar") : new Foo(1); 
int baz = bar.Value; 

Ist das ein Compiler-Fehler?

Ich denke "Behandle sie alle gleich" (zumindest so, wie du es beschrieben hast) und "Kompilierzeit Fehler" werden sich gegenseitig ausschließen.

In jedem Fall denke ich, der "beste Weg" wird ein Kompromiss zwischen Generika und Vererbung sein. Sie können eine Foo<T> definieren, die eine Unterklasse von Foo ist; Dann können Sie noch Sammlungen von Foo haben.

abstract public class Foo 
{ 
    // Common implementation 

    abstract public object ObjectValue { get; } 
} 

public class Foo<T> : Foo 
{ 
    public Foo(T initialValue) 
    { 
     Value = initialValue; 
    } 

    public T Value { get; set; } 

    public object ObjectValue 
    { 
     get { return Value; } 
    } 
} 
+0

Wahr. Ich hatte nicht über Runtimplikationen nachgedacht. Guter Punkt, danke. –

+0

@Mystere Man: Du wirst es in beide Richtungen werfen müssen; Per Definition weiß etwas, das nur ein Foo ist, nicht, was sein Werttyp ist; Wenn ja, warum hätten Sie die Basisklasse verwendet? (anstelle des Typs = spezifische Unterklasse) –

+0

@Mystere Man: Ich nehme an, wenn Sie benötigt würden, könnten Sie die Basisklasse den Wert als ein Objekt zurückgeben. –

0

Eine Sache ist es, jeden Typ in Ihrem internen Zustand der Klasse zu speichern, und ein anderer ist, ihn extern zu belichten. Wenn Sie eine Klasse schreiben, erklären Sie einen Vertrag für sein Verhalten. Die Art und Weise, wie Sie es schreiben, wird stark beeinflussen, wie Client-Code aussehen wird, wenn Sie die Klasse verwenden.

Wenn Sie beispielsweise die Schnittstelle IConvertible implementieren, geben Sie an, dass Ihr Typ in einen beliebigen CLR-Typ als äquivalenter Wert konvertiert werden kann.

Ich habe auch Implementierungen gesehen, wo eine Value-Klasse verwendet wurde, um Ergebnisse von Berechnungen zu speichern, Ergebnisse, die entweder einen String, Double, Int oder Boolean darstellen könnten. Das Problem war jedoch, dass der Client-Code eine Value.Type-Eigenschaft einer enum {String, Integer, Double, Boolean} überprüfen und dann entweder die Value.Value-Eigenschaft (die extern von der Value-Klasse als Objekttyp verfügbar gemacht wurde)) oder verwenden Sie die spezifischen Werte ValueString, ValueDouble, ValueInt, ValueBoolean.

0

Warum nicht einfach string, double und int verwenden?


Nach Informationen über die Sammlung: Was ist mit der Verwendung von object? Sie müssen danach trotzdem auf Typen und solche prüfen. Und um Ihnen dabei zu helfen, können Sie die Operatoren is und as verwenden. Und die Enumerable.Cast Method, oder noch besser, die Enumerable.OfType Method. für jedes Ihrer Objekt

+0

Weil ich versuche, ein Objekt zu erstellen, das in einer Sammlung gespeichert werden kann (tatsächlich über einen Indexer verwendet), die alle möglichen Typen repräsentiert, die für das Objekt gültig sind. –

+0

was ist dann mit Objekt verwenden? – Svish

+0

Wie ich in meiner Frage gesagt habe, war Objekt eine mögliche Lösung, aber keine, die ich wegen des Castings mochte.Ich möchte auch keine Objekte von einem Typ in einen anderen umwandeln oder konvertieren, sondern nur den Typ der Daten zurückgeben. –

1

Viele Systeme verwenden ein Hilfsmethoden der alternativen Arten zurückzukehren ebenso wie die .net-Frameworks Basisobjekt der toString() -Methode

hat wählen, welche die beste Basistyp ist, und Verfahren sorgen für andere Fälle

z

class Foo{ 
    public Int32 Value { get; set; } 
    public Byte ToByte() { return Convert.ToByte(Value); } 
    public Double ToDouble() { return (Double)Value; } 
    public new String ToString() { return Value.ToString("#,###"); } 
} 
+0

Das Problem hier ist, dass ich nicht versuche, einen Wert in andere Typen zu konvertieren, ich versuche, den richtigen Typ für den Wert zurückzugeben. Wenn es ein Double ist, möchte ich es nicht in einen int konvertieren. –

0

Eigentlich, was ist der Zweck dieser Klasse? Das größte Problem scheint Design zu sein, das mindestens SRP (Single-Responsibility-Prinzip) bricht.

Nichtsdestotrotz, wenn ich es richtig lese, möchten Sie einen Wert im Container speichern, den Container an den Client übergeben und den Wert typsicher eingeben.

Mit diesem Ansatz können Sie Ihren Vorschlag verwenden, d. H.

In diesem Fall, warum nicht einfach Daten als Objekt übergeben und selbst zu werfen?

Je nach Bedarf können Sie auch versuchen, "PHP in C#":

namespace Project1 { 
public class Class1 { 
    static int Main(string[] args) { 
     MyInt a = 1; 
     MyInt b = "2"; 

     Console.WriteLine(a + b); // writes 3 

     Console.ReadLine(); 

     return 0; 
    } 
} 


class MyInt { 
    private int value; 

    public static implicit operator int(MyInt container) { 
     return container.value; 
    } 

    public static implicit operator MyInt(int value) { 
     MyInt myInt = new MyInt(); 
     myInt.value = value; 
     return myInt ; 
    } 

    public static implicit operator MyInt(string stringedInt) { 
     MyInt myInt = new MyInt(); 
     myInt.value = int.Parse(stringedInt); 
     return myInt; 
    } 
} 
} 
+0

Nein, ich glaube nicht, dass es eine Verletzung der SRP ist. Die Daten werden für den gleichen Zweck verwendet, es ist also eine einzige Verantwortung. Es ist nur in verschiedenen Typen verfügbar und ich möchte keine int-Daten in Doubles (mit all der Boshaftigkeit von IEEE zu behandeln) oder Strings aufgrund der Leistung konvertieren. –

+0

Ihre Lösungen sind im Grunde alle bereits in meiner Frage beschrieben und aus verschiedenen Gründen abgelehnt. Ich werde sie wahrscheinlich erneut untersuchen und Kompromisse eingehen müssen. –

+0

Nun, die Frage ist, was der Verbraucher der Klasse wirklich mit dem Wert machen will. Wenn Sie ein Beispiel für den Code angeben könnten, der den Wert verwendet ... – user76035

1

Es tut mir leid, ich Ihre Prämisse einfach nicht kaufen. Wenn die Daten alle den gleichen Zweck haben, sollten sie alle den gleichen Typ haben. Stellen Sie sich eine Klasse vor, die die aktuelle Temperatur halten soll, wie sie von einem von mehreren Webdiensten zurückgegeben wird. Alle Dienste geben die Temperatur in Celsius zurück. Aber man gibt als int zurück, man kehrt als Doppel zurück, und man gibt es als String zurück.

Es sind nicht drei verschiedene Typen - es ist ein Typ - doppelt. Sie müssten einfach die nicht doppelten Renditen in Double konvertieren, was die Temperatur ist (oder float).

Im Allgemeinen, wenn Sie mehrere Darstellungen einer Sache haben, dann ist es immer noch eine Sache, nicht mehrere Dinge. Konvertieren Sie die mehreren Darstellungen in eins.

+0

Im Wesentlichen ist dies ein Feld einer Datenbank (obwohl es keine Datenbank ist). Es gibt 100 Felder, und jedes Feld kann einen von drei Arten von Daten enthalten, und es gibt keine Möglichkeit, vorherzusagen, welches das ist (das kommt von einem externen Dienst, den ich nicht kontrollieren kann). Ich muss die Werte polymorph behandeln –

+0

, so dass sie im Wesentlichen "die gleiche Sache" sind, obwohl sie von verschiedenen Arten sein können. Würde ich die "Datenbank" so gestalten? Nein, aber ich habe keine Wahl in der Sache. –