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?


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;
}

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;
}

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ń.


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.