C # - wiele typów ogólnych na jednej liście

153

To chyba nie jest możliwe, ale mam taką klasę:

public class Metadata<DataType> where DataType : struct
{
    private DataType mDataType;
}

Jest w tym coś więcej, ale nie komplikujmy. Typ ogólny (DataType) jest ograniczony do typów wartości przez instrukcję where. To, co chcę zrobić, to mieć listę tych obiektów metadanych różnych typów (DataType). Jak na przykład:

List<Metadata> metadataObjects;
metadataObjects.Add(new Metadata<int>());
metadataObjects.Add(new Metadata<bool>());
metadataObjects.Add(new Metadata<double>());

Czy to w ogóle możliwe?

Carl
źródło
24
Zastanawiam się, czy podejście w poniższych odpowiedziach daje jakąś realną korzyść w porównaniu z samym użyciem List<object>? Nie przestaną boksować / rozpakowywać, nie usuwają potrzeby odlewania, a ostatecznie otrzymujesz Metadataobiekt, który nie mówi ci nic o faktycznym DataType, szukałem rozwiązania tych problemów. Jeśli zamierzasz zadeklarować interfejs / klasę, tylko po to, aby móc umieścić implementujący / wyprowadzony typ ogólny na liście ogólnej, jak różni się to od używania List<object>innej niż posiadanie bezsensownej warstwy?
Saeb Amini
9
Zarówno abstrakcyjna klasa bazowa, jak i interfejs zapewniają pewien stopień kontroli, ograniczając typ elementów, które można dodać do listy. Nie rozumiem też, jak ma w tym boks.
0b101010
3
Oczywiście, jeśli używasz .NET v4.0 lub nowszego, rozwiązaniem jest kowariancja. List<Metadata<object>>Zrób sztuczkę.
0b101010
2
@ 0b101010 Myślałem tak samo, ale niestety wariancja nie jest dozwolona w przypadku typów wartości. Ponieważ OP ma structograniczenie, tutaj nie działa. Zobacz
nawfal
@ 0b101010, Oba ograniczają tylko typy odwołań, nadal można dodać dowolny wbudowany typ wartości i każdą strukturę. Ponadto, na końcu, masz listę MetaDatatypów referencyjnych zamiast oryginalnych typów wartości bez informacji (w czasie kompilacji) o bazowym typie wartości każdego elementu, co jest efektywnym „opakowaniem”.
Saeb Amini,

Odpowiedzi:

195
public abstract class Metadata
{
}

// extend abstract Metadata class
public class Metadata<DataType> : Metadata where DataType : struct
{
    private DataType mDataType;
}
leppie
źródło
5
Łał! Naprawdę nie sądziłem, że to możliwe! Ratujesz życie, człowieku!
Carl
2
+10 za to! Nie wiem, dlaczego to się kompiluje… Dokładnie to, czego potrzebowałem!
Odys,
Mam podobny problem, ale moja klasa ogólna rozciąga się od innej klasy ogólnej, więc nie mogę użyć twojego rozwiązania ... jakieś pomysły dotyczące rozwiązania tej sytuacji?
Sheridan
10
Czy takie podejście ma jakieś zalety w porównaniu z prostym List<object>? proszę spojrzeć na mój komentarz zamieszczony pod pytaniem OP.
Saeb Amini
11
@SaebAmini Lista <obiekt> nie pokazuje deweloperowi żadnych zamiarów ani nie zapobiega programiście strzelenia sobie w stopę przez błędne dodanie do listy obiektu innego niż MetaData. Używając List <MetaData>, zrozumiałe jest, co powinna zawierać lista. Najprawdopodobniej Metadane będą miały pewne właściwości / metody publiczne, które nie zostały pokazane w powyższych przykładach. Dostęp do nich przez obiekt wymagałby nieporęcznej obsady.
Buzz
92

Idąc za odpowiedzią Leppiego, dlaczego nie stworzyć MetaDatainterfejsu:

public interface IMetaData { }

public class Metadata<DataType> : IMetaData where DataType : struct
{
    private DataType mDataType;
}
bruno conde
źródło
Czy ktoś może mi powiedzieć, dlaczego takie podejście jest lepsze?
Lazlo
34
Ponieważ żadna wspólna funkcjonalność nie jest udostępniana - po co więc marnować na to klasę bazową? Interfejs jest wystarczający
flq
2
Ponieważ możesz implementować interfejsy w struct.
Damian Leszczyński - Vash
2
Dziedziczenie klas przy użyciu metod wirtualnych jest jednak około 1,4x szybsze niż metody interfejsu. Jeśli więc planujesz zaimplementować jakiekolwiek nieogólne metody / właściwości MetaData (wirtualne) w MetaData <DataType>, wybierz klasę abstrakcyjną zamiast interfejsu, jeśli zależy Ci na wydajności. W przeciwnym razie korzystanie z interfejsu może być bardziej elastyczne.
TamusJRoyce
30

Użyłem również wersji innej niż generyczna, używając newsłowa kluczowego:

public interface IMetadata
{
    Type DataType { get; }

    object Data { get; }
}

public interface IMetadata<TData> : IMetadata
{
    new TData Data { get; }
}

Jawna implementacja interfejsu jest używana, aby umożliwić obu Dataczłonkom:

public class Metadata<TData> : IMetadata<TData>
{
    public Metadata(TData data)
    {
       Data = data;
    }

    public Type DataType
    {
        get { return typeof(TData); }
    }

    object IMetadata.Data
    {
        get { return Data; }
    }

    public TData Data { get; private set; }
}

Możesz uzyskać wersję kierowania na typy wartości:

public interface IValueTypeMetadata : IMetadata
{

}

public interface IValueTypeMetadata<TData> : IMetadata<TData>, IValueTypeMetadata where TData : struct
{

}

public class ValueTypeMetadata<TData> : Metadata<TData>, IValueTypeMetadata<TData> where TData : struct
{
    public ValueTypeMetadata(TData data) : base(data)
    {}
}

Można to rozszerzyć na dowolny rodzaj ogólnych ograniczeń.

Bryan Watts
źródło
4
+1 tylko dlatego, że pokazujesz, jak go używać ( DataTypei bardzo object Datapomogłeś)
Odys,
4
Na przykład nie potrafię pisać Deserialize<metadata.DataType>(metadata.Data);. Mówi mi, że nie można rozwiązać metadanych symboli . Jak pobrać DataType, aby użyć go do metody ogólnej?
Cœur