[Nota: questa domanda aveva il titolo originale " Unione in stile C (ish) in C # " ma, come mi ha informato il commento di Jeff, a quanto pare questa struttura è chiamata "unione discriminata"]
Scusa la verbosità di questa domanda.
Ci sono un paio di domande simili che suonano già in SO, ma sembrano concentrarsi sui vantaggi di risparmio di memoria dell'unione o sull'utilizzo per l'interoperabilità. Ecco un esempio di tale domanda .
Il mio desiderio di avere una cosa di tipo sindacale è in qualche modo diverso.
Al momento sto scrivendo del codice che genera oggetti che assomigliano un po 'a questo
public class ValueWrapper
{
public DateTime ValueCreationDate;
// ... other meta data about the value
public object ValueA;
public object ValueB;
}
Cose piuttosto complicate, penso che sarai d'accordo. Il fatto è che ValueA
può essere solo di un paio di certi tipi (diciamo string
, int
e Foo
(che è una classe), e ValueB
può essere un altro piccolo insieme di tipi. Non mi piace trattare questi valori come oggetti (voglio il caldo comodamente sensazione di codifica con un po 'di sicurezza di tipo).
Quindi ho pensato di scrivere una piccola classe wrapper banale per esprimere il fatto che ValueA logicamente è un riferimento a un tipo particolare. Ho chiamato la classe Union
perché quello che sto cercando di ottenere mi ha ricordato il concetto di unione in C.
public class Union<A, B, C>
{
private readonly Type type;
public readonly A a;
public readonly B b;
public readonly C c;
public A A{get {return a;}}
public B B{get {return b;}}
public C C{get {return c;}}
public Union(A a)
{
type = typeof(A);
this.a = a;
}
public Union(B b)
{
type = typeof(B);
this.b = b;
}
public Union(C c)
{
type = typeof(C);
this.c = c;
}
/// <summary>
/// Returns true if the union contains a value of type T
/// </summary>
/// <remarks>The type of T must exactly match the type</remarks>
public bool Is<T>()
{
return typeof(T) == type;
}
/// <summary>
/// Returns the union value cast to the given type.
/// </summary>
/// <remarks>If the type of T does not exactly match either X or Y, then the value <c>default(T)</c> is returned.</remarks>
public T As<T>()
{
if(Is<A>())
{
return (T)(object)a; // Is this boxing and unboxing unavoidable if I want the union to hold value types and reference types?
//return (T)x; // This will not compile: Error = "Cannot cast expression of type 'X' to 'T'."
}
if(Is<B>())
{
return (T)(object)b;
}
if(Is<C>())
{
return (T)(object)c;
}
return default(T);
}
}
L'utilizzo di questa classe ValueWrapper ora ha questo aspetto
public class ValueWrapper2
{
public DateTime ValueCreationDate;
public Union<int, string, Foo> ValueA;
public Union<double, Bar, Foo> ValueB;
}
che è qualcosa di simile a ciò che volevo ottenere ma mi manca un elemento abbastanza cruciale: il controllo del tipo applicato dal compilatore quando si chiamano le funzioni Is e As come dimostra il codice seguente
public void DoSomething()
{
if(ValueA.Is<string>())
{
var s = ValueA.As<string>();
// .... do somethng
}
if(ValueA.Is<char>()) // I would really like this to be a compile error
{
char c = ValueA.As<char>();
}
}
IMO Non è valido chiedere a ValueA se è a char
poiché la sua definizione dice chiaramente che non lo è - questo è un errore di programmazione e vorrei che il compilatore lo prendesse. [Inoltre, se potessi farlo correttamente, allora (si spera) diventerei anche intellisense, il che sarebbe un vantaggio.]
Per ottenere ciò, vorrei dire al compilatore che il tipo T
può essere A, B o C.
public bool Is<T>() where T : A
or T : B // Yes I know this is not legal!
or T : C
{
return typeof(T) == type;
}
Qualcuno ha idea se quello che voglio ottenere è possibile? O sono semplicemente stupido per aver scritto questa lezione in primo luogo?
Grazie in anticipo.
StructLayout(LayoutKind.Explicit)
eFieldOffset
. Questo non può essere fatto con i tipi di riferimento, ovviamente. Quello che stai facendo non è affatto come un'unione C.