[Uwaga: to pytanie miało oryginalny tytuł „ Unia w stylu C (ish) w C # ”, ale jak poinformował mnie komentarz Jeffa, najwyraźniej ta struktura jest nazywana „unią dyskryminowaną”]
Przepraszam za gadatliwość tego pytania.
Jest kilka podobnie brzmiących pytań, które mogę zadać już w SO, ale wydają się one koncentrować na korzyściach wynikających z oszczędzania pamięci przez związek lub używanie go do współpracy. Oto przykład takiego pytania .
Moje pragnienie posiadania czegoś w rodzaju związku jest nieco inne.
W tej chwili piszę kod, który generuje obiekty wyglądające trochę tak
public class ValueWrapper
{
public DateTime ValueCreationDate;
// ... other meta data about the value
public object ValueA;
public object ValueB;
}
Dość skomplikowane rzeczy, myślę, że się zgodzisz. Chodzi o to, że ValueA
może to być tylko kilka określonych typów (powiedzmy string
, int
i Foo
(co jest klasą) i ValueB
może to być kolejny mały zestaw typów. Nie lubię traktować tych wartości jako obiektów (chcę, aby ciepłe, przytulne uczucie kodowanie z odrobiną bezpieczeństwa typu).
Pomyślałem więc o napisaniu trywialnej małej klasy opakowującej, aby wyrazić fakt, że ValueA jest logicznie odwołaniem do określonego typu. Zadzwoniłem do klasy, Union
ponieważ to, co próbuję osiągnąć, przypomniało mi koncepcję związku w 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);
}
}
Używanie tej klasy ValueWrapper wygląda teraz tak
public class ValueWrapper2
{
public DateTime ValueCreationDate;
public Union<int, string, Foo> ValueA;
public Union<double, Bar, Foo> ValueB;
}
co jest czymś podobnym do tego, co chciałem osiągnąć, ale brakuje mi jednego dość kluczowego elementu - czyli sprawdzania typu wymuszonego przez kompilator podczas wywoływania funkcji Is i As, jak pokazuje poniższy kod
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 Nie można pytać ValueA, czy jest a, char
ponieważ jego definicja wyraźnie mówi, że tak nie jest - jest to błąd programowania i chciałbym, aby kompilator to wychwycił. [Również gdybym mógł to zrobić poprawnie, wtedy (miejmy nadzieję) dostałbym również intelisense - co byłoby dobrodziejstwem.]
Aby to osiągnąć, chciałbym powiedzieć kompilatorowi, że T
może to być typ A, B lub 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;
}
Czy ktoś ma pojęcie, czy to, co chcę osiągnąć, jest możliwe? A może po prostu jestem po prostu głupi, że piszę te zajęcia w pierwszej kolejności?
Z góry dziękuję.
StructLayout(LayoutKind.Explicit)
iFieldOffset
. Oczywiście nie można tego zrobić w przypadku typów referencyjnych. To, co robisz, wcale nie przypomina Unii C.