[Примітка: Це запитання мало оригінальну назву „ Союз стилю C (ish) у C # “, але, як повідомив коментар Джеффа, очевидно, ця структура називається „дискримінованим об’єднанням“]
Вибачте за багатослівність цього питання.
Є кілька подібних звучачих запитань до мого вже в SO, але вони, схоже, зосереджені на перевагах об'єднання, що економить пам'ять, або використанні його для взаємодії. Ось приклад такого питання .
Моє бажання мати щось на зразок профспілок дещо інше.
На даний момент я пишу якийсь код, який генерує об’єкти, які виглядають приблизно так
public class ValueWrapper
{
public DateTime ValueCreationDate;
// ... other meta data about the value
public object ValueA;
public object ValueB;
}
Дуже складна штука, думаю, ви погодитесь. Річ у тім, що ValueA
може бути лише декількох певних типів (скажімо string
, int
і Foo
(що є класом), а ValueB
може бути ще одним невеликим набором типів. Я не люблю ставитися до цих значень як до об’єктів (я хочу тепле приємне відчуття кодування з невеликим типом безпеки).
Тому я задумався над написанням тривіального маленького класу обгортки, щоб висловити той факт, що ValueA логічно є посиланням на певний тип. Я зателефонував до класу, Union
бо те, чого я намагаюся досягти, нагадало мені про концепцію об’єднання в 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);
}
}
Використання цього класу ValueWrapper тепер виглядає так
public class ValueWrapper2
{
public DateTime ValueCreationDate;
public Union<int, string, Foo> ValueA;
public Union<double, Bar, Foo> ValueB;
}
що є чимось на зразок того, чого я хотів досягти, але мені бракує одного досить важливого елемента - це перевірка типом компілятора при виклику функцій Is та As, як показано в наступному коді
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 Неправильно запитувати ValueA, якщо це так, char
оскільки його визначення чітко говорить, що це не так - це помилка програмування, і я хотів би, щоб компілятор вирішив цю проблему . [Крім того, якби я міг отримати це правильно, тоді (сподіваюся), я б також отримав intellisense - що було б благом.]
Для досягнення цього я хотів би сказати компілятору, що тип T
може бути одним з A, B або 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;
}
Хтось уявляє, чи можливо те, чого я хочу досягти? Або я просто дурний, що спочатку писав цей клас?
Заздалегідь спасибі.
StructLayout(LayoutKind.Explicit)
таFieldOffset
. Звичайно, цього не можна зробити з еталонними типами. Те, що ви робите, зовсім не схоже на С Союз.