[Observação: esta questão tinha o título original " União de estilo C (ish) em C # ", mas, como o comentário de Jeff me informou, aparentemente essa estrutura é chamada de 'união discriminada']
Desculpe a verbosidade desta pergunta.
Já existem algumas perguntas semelhantes às minhas no SO, mas elas parecem se concentrar nos benefícios de economia de memória do sindicato ou em usá-lo para interoperabilidade. Aqui está um exemplo de tal pergunta .
Meu desejo de ter um tipo de sindicato é um pouco diferente.
Estou escrevendo um código no momento que gera objetos que se parecem um pouco com este
public class ValueWrapper
{
public DateTime ValueCreationDate;
// ... other meta data about the value
public object ValueA;
public object ValueB;
}
Coisas muito complicadas, acho que você vai concordar. O fato é que ValueA
só podem ser de alguns tipos (digamos string
, int
e Foo
(que é uma classe) e ValueB
podem ser outro pequeno conjunto de tipos. Não gosto de tratar esses valores como objetos (quero a sensação aconchegante de codificação com um pouco de segurança de tipo).
Portanto, pensei em escrever uma pequena classe de wrapper trivial para expressar o fato de que ValueA logicamente é uma referência a um tipo específico. Liguei para a classe Union
porque o que estou tentando alcançar me lembrou do conceito de união em 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);
}
}
Usando esta classe ValueWrapper agora se parece com isto
public class ValueWrapper2
{
public DateTime ValueCreationDate;
public Union<int, string, Foo> ValueA;
public Union<double, Bar, Foo> ValueB;
}
que é algo parecido com o que eu queria alcançar, mas estou faltando um elemento bastante crucial - que é a verificação de tipo forçada do compilador ao chamar as funções Is e As, como o código a seguir demonstra
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 Não é válido perguntar a ValueA se é um, char
já que sua definição diz claramente que não é - isso é um erro de programação e eu gostaria que o compilador pegasse nisso. [Além disso, se eu pudesse fazer isso corretamente, então (espero) eu também obteria intellisense - o que seria uma bênção.]
Para conseguir isso, gostaria de dizer ao compilador que o tipo T
pode ser A, B ou 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;
}
Alguém tem ideia se o que quero alcançar é possível? Ou eu sou simplesmente estúpido por escrever esta classe em primeiro lugar?
Desde já, obrigado.
StructLayout(LayoutKind.Explicit)
eFieldOffset
. Isso não pode ser feito com tipos de referência, é claro. O que você está fazendo não é nada parecido com um C Union.