2009-02-26 4 views
23

die folgende generische Klasse zu haben, die entweder string, int, float, long als Typ enthalten würde:C#: Eine Liste generischer Klassen mit unterschiedlichen Typen deklarieren und verwenden, wie?

public class MyData<T> 
{ 
    private T _data; 

    public MyData (T value) 
    { 
     _data = value; 
    } 

    public T Data { get { return _data; } } 
} 

Ich versuche, eine Liste von MyData<T> zu bekommen, wo jeder Gegenstand verschiedener T wäre.

Ich mag ein Element in der Lage sein, aus der Liste zuzugreifen und seinen Wert wie im folgenden Code zu erhalten:

MyData<> myData = _myList[0]; // Could be <string>, <int>, ... 
SomeMethod (myData.Data); 

wo SomeMethod() wie folgt erklärt:

public void SomeMethod (string value); 
public void SomeMethod (int value); 
public void SomeMethod (float value); 

UPDATE:

SomeMethod() ist von einer anderen Tier-Klasse Ich habe keine Kontrolle über und SomeMethod(object) existiert nicht.


Allerdings kann ich nicht scheinen, einen Weg zu finden, den Compiler glücklich zu machen.

Irgendwelche Vorschläge?

Vielen Dank.

+0

Könnten Sie erarbeiten, was Ihr tut Somemethod? Wir können Ihnen vielleicht besser helfen. –

+0

Was SomeMethod tut, ist hier nicht relevant. Ich füge einfach hinzu, dass es keine Definition für SomeMethod (Objekt) gibt. –

+0

Ihr Grunddesign ist fehlerhaft. Ich würde vorschlagen, eine Frage zu stellen, in der Sie die Anforderungen und Ihre Lösung detailliert beschreiben und nach Vorschlägen fragen. – Will

Antwort

8

Teilnehmer können das wirklich vereinfachen und haben immer noch einige Dinge, typsicher:

public void TestMethod1() 
{ 
    Action<SomeClass, int> intInvoke = (o, data) => o.SomeMethod(data); 
    Action<SomeClass, string> stringInvoke = (o, data) => o.SomeMethod(data); 

    var list = new List<MyData> 
    { 
     new MyData<int> { Data = 10, OnTypedInvoke = intInvoke }, 
     new MyData<string> { Data = "abc", OnTypedInvoke = stringInvoke } 
    }; 

    var someClass = new SomeClass(); 
    foreach (var item in list) 
    { 
     item.OnInvoke(someClass); 
    } 
} 

public abstract class MyData 
{ 
    public Action<SomeClass> OnInvoke; 
} 

public class MyData<T> : MyData 
{ 
    public T Data { get; set; } 
    public Action<SomeClass, T> OnTypedInvoke 
    { set { OnInvoke = (o) => { value(o, Data); }; } } 
} 

public class SomeClass 
{ 
    public void SomeMethod(string data) 
    { 
     Console.WriteLine("string: {0}", data); 
    } 

    public void SomeMethod(int data) 
    { 
     Console.WriteLine("int: {0}", data); 
    } 
} 
+0

Ausgezeichnete Augenöffner. – Telemat

0

Erben MyData<T> von einer nicht-generischen MyData Klasse und machen Sie eine Liste davon.

Auf diese Weise können Sie die Überlastung nicht automatisch beheben. Sie müssen es manuell tun.

abstract class MyData { 
    protected abstract object GetData(); 
    protected abstract Type GetDataType(); 
    public object Data { 
     get { return GetData(); } 
    } 
    public Type DataType { 
     get { return GetDataType(); } 
    } 
} 

class MyData<T> : MyData { 
    protected override object GetData() { return Data; } 
    protected override Type GetDataType() { return typeof(T); } 
    public new T Data { 
    get { ... } 
    } 
} 
+0

Ok, jetzt würde ich es verwenden wie: Liste Liste = neue Liste (); list.Add (neue MyData ()); list.Add (neue MyData ()); SomeMethod (Liste [0] .Data); Irgendwie muss SomeMethod() ein Objekt akzeptieren. Das brauche ich leider nicht. Danke für die Antwort. Stecy –

+0

Ja, deshalb habe ich gesagt, dass Sie Überladungen manuell verwalten sollten, indem Sie den Typ berücksichtigen. Es gibt keinen direkten Weg, der von Generika unterstützt wird. –

1

In diesem Fall müssen Sie MyData<object> denn das ist die einzige Sache ist, gemeinsam diese Typen haben.

+2

Wie ermöglicht MyData den Aufruf von SomeMethod (int)? –

0

Generics ermöglichen es Ihnen, eine Art für die ganze Liste angeben, wenn Sie die Liste, zum Beispiel eine Liste zum Speichern von int schaffen würde wie diese

var myData = new MyData<int>(); 

erstellt werden Wenn Sie mehrere Arten in der gleichen speichern möchten Generische Liste Sie können einen gemeinsamen Basistyp oder eine Schnittstelle für diese Typen angeben. In Ihrem Fall wäre der einzige gemeinsame Basistyp für die Typen, die Sie speichern möchten, das Objekt.

Aber Sie können einfach die nicht-generische Liste zum Speichern von Objekten verwenden.

6

Verwenden Sie einfach eine ArrayList und vergessen Sie den MyData<T> Typ.

ArrayList myStuff = getStuff(); 
float x = myStuff.OfType<float>().First(); 
SomeMethod(x); 
string s = myStuff.OfType<string>().First(); 
SomeMethod(s); 

Das Problem mit MyData<T> ist, dass Sie den Compiler erwar eine Art zu überprüfen, die erst zur Laufzeit bekannt ist. Compiler überprüfen Typen, die zum Zeitpunkt der Kompilierung bekannt sind.

+0

Danke! Dies war die Antwort auf mein persönliches Problem. Ich versuchte, eine Liste von BlockingCollection zu verfolgen, und dann über eine GetQueue wo T: Interface-Methode, ob eine Warteschlange des Typs T bereits in meiner Liste vorhanden war oder nicht, und die neue leere Warteschlange instanziieren und zurückgeben. Aber BlockingCollection kann nicht implizit in BlockingCollection gegossen werden, auch wenn T: Interface, und ich konnte nicht für das Leben von mir finden die richtige kovariante/contravariant/Delegate-Implementierung, um dies zu tun – Isaac

3

Sie können es nicht so machen, wie Sie möchten.

Wenn eine Instanz einer generischen Klasse initialisiert wird, ist sie an einen bestimmten Typ gebunden. Da Sie Objekte unterschiedlicher Typen in Ihrer Liste speichern möchten, müssen Sie eine Instanz erstellen, die an den kleinsten gemeinsamen Nenner gebunden ist - in Ihrem Fall ist es Objekt.

Das bedeutet jedoch, dass die Data-Eigenschaft jetzt ein Objekt vom Typ Object zurückgibt. Der Compiler kann den eigentlichen Datentyp zur Kompilierzeit nicht ableiten, so dass er die entsprechende SomeMethod Überladung auswählen kann.

Sie müssen entweder eine Überladung von SomeMethod bereitstellen, die Object als Parameter verwendet, oder die Anforderung entfernen, verschiedene unterschiedliche Typen in Ihrer Sammlung zu speichern.

Oder Sie können mit einer Standard-IEnumerable Sammlung (wie Array) gehen und die OfType<> Erweiterungsmethode verwenden, um die Teilmenge der Sammlung eines bestimmten Typs zu erhalten.

+0

Ich hatte Angst, würde es auf ein Objekt zu lösen. .. Allerdings kann ich ein Objekt nicht verwenden, da SomeMethod aus einer Bibliothek stammt, und ich kann die Tatsache nicht ändern, dass es ein Objekt nicht akzeptiert ... Außerdem möchte ich nicht wechseln () auf den Typ der Variablen ... –

1

Vorgeschlagene Platzhalter eine Weile zurück here. Geschlossen als "wird nicht behoben" :(

13

Ich denke, das Problem, das Sie haben, ist, weil Sie versuchen, einen generischen Typ zu erstellen, und erstellen Sie dann eine Liste dieser generischen Art. Sie könnten erreichen, was Sie Versuchen Sie, die Datentypen, die Sie unterstützen möchten, auszugleichen, sagen Sie als IData-Element, und erstellen Sie dann Ihr MyData-Generic mit einer Einschränkung von IData. Der Nachteil dabei wäre, dass Sie Ihre eigenen erstellen müssten Datentypen alle primitiven Datentypen repräsentieren Sie verwenden (string, int, float, long) Es könnte etwa so aussehen:.

public class MyData<T, C> 
    where T : IData<C> 
{ 
    public T Data { get; private set; } 

    public MyData (T value) 
    { 
     Data = value; 
    } 
} 

public interface IData<T> 
{ 
    T Data { get; set; } 
    void SomeMethod(); 
} 

//you'll need one of these for each data type you wish to support 
public class MyString: IData<string> 
{ 
    public MyString(String value) 
    { 
     Data = value; 
    } 

    public void SomeMethod() 
    { 
     //code here that uses _data... 
     Console.WriteLine(Data); 
    } 

    public string Data { get; set; } 
} 

und dann bist du Implementierung wäre so etwas wie:

var myData = new MyData<MyString, string>(new MyString("new string"));  
// Could be MyString, MyInt, ... 
myData.Data.SomeMethod(); 

es ist ein wenig mehr Arbeit, aber Sie bekommen die Funktionalität, die Sie wollten.

UPDATE: entfernen Somemethod von Ihrer Schnittstelle und tun nur das

SomeMethod(myData.Data.Data); 
+0

Schade, ich muss die primitiven Typen einpacken, aber das ist etwas, was ich bereit bin zu akzeptieren, da es keine andere Möglichkeit gibt. In Ihrem Beispiel nahmen Sie jedoch an, dass SomeMethod() Teil von MyData war, was nicht der Fall ist (Ich habe die Frage aktualisiert). –

+0

eigentlich können Sie die gleiche Sache erreichen, ich wickelte es so, dass es mehr eingekapselt wäre, siehe mein Update – Joseph

+0

Also ... wie platziert man MyData und MyData in die gleiche Sammlung? –

1

Sie können für SomeMethod einen generischen Wrapper erstellen und für die Art des generischen Argument überprüfen, dann auf die entsprechende Methode delegieren.

public void SomeMethod<T>(T value) 
{ 
    Type type = typeof(T); 

    if (type == typeof(int)) 
    { 
     SomeMethod((int) (object) value); // sadly we must box it... 
    } 
    else if (type == typeof(float)) 
    { 
     SomeMethod((float) (object) value); 
    } 
    else if (type == typeof(string)) 
    { 
     SomeMethod((string) (object) value); 
    } 
    else 
    { 
     throw new NotSupportedException(
      "SomeMethod is not supported for objects of type " + type); 
    } 
} 
+0

Danke für deine Antwort. Ich fürchte, ich werde schließlich Boxen/Unboxing verwenden müssen. Ich dachte, dass es einen effizienteren Weg geben muss. –

Verwandte Themen