Проблема заключается в следующем:
Я хочу универсальную функцию, которая имеет выходной параметр универсального типа.
Ограничьте универсальный тип до ref-type, и, конечно, проблем нет.
Но я хотел совершенно неограниченный родовой тип! Нет ограничений new () или class / struct!
public class A
{ }
public class B<T> : A
{
public T t;
public B(T i)
{
this.t = t;
}
}
public static class S
{
public static bool Test<T>(ref A a, out T t)
{
C<T> c = a as C<T>;
if(c != null)
{
t = c.t;
return true;
}
else
return false;
}
}
class Run
{
static void Main(string[] args)
{
C<SomeType> c = new C<SomeType>(new SomeType(...));
SomeType thing;
S.Test<SomeType>(c,thing);
}
}
Приведенный выше код иллюстрирует то, что я хочу сделать. Я хочу установить выходной параметр, но только при условии, схожем с изображенным. В ложном случае Test(...)
я совершенно не заинтересован в значении out t
. Но вышеизложенное, конечно, не рабочий код.
Проблема выше заключается в том, что out
параметр должен быть инициализирован. Но, может быть, иногда инициализация стоит дорого (зависит от типа T
), и я не хочу инициализировать фиктивный экземпляр класса только для того, чтобы компилятор перестал жаловаться. Таким образом, возникает вопрос: как инициализировать неизвестный тип (и убедиться, что он инициализирован нулем, если это класс)?
Теоретически, вы должны написать что-то вроде
public static bool Test<T>(ref A a, out T t)
{
//...
else
{
if(typeof(T).IsValueType)
{
t = (T)0; //Can't cast from int to T error (Yeah, this is a little tricky...)
//OR:
t = new T(); //No-no, doesn't have the new()-restriction on T (But we know it's a value type... life sucks)
}
else
t = null; //Error: Can't set to null! T could be valueType! (No it CAN'T... again life sucks)
return false;
}
}
Но, увы, не все так просто. Первая проблема заключается в том, что когда T является типом значения, мы должны иметь возможность его создать, но компилятор не позволит. Вторая проблема похожа: «Это может быть тип значения!» - нет, я просто убедился, что это не так. Это должно работать, но это не так. Очень раздражает.
Ok. Итак, мы начинаем творчески подходить ... в конце концов, есть этот замечательный класс с именем Object, и он имеет особое отношение ко всем вещам C # 'ish.
public static bool Test<T>(ref A a, out T t)
{
//...
else
{
if(typeof(T).IsValueType)
{
t = (T)(object)0; //Works ONLY if T is valuetype: int, otherwise you get a "must be less than infinity"-error.
}
else
{
t = (T)(object)null; //Null ref exception in the cast...
}
return false;
}
}
компилирует по крайней мере. Но это все равно мусор. Runtime-ошибка в изобилии.
Проблема со значением-типом заключается в том, что объектный тип запоминает, какой это тип на самом деле , и при попытке привести его к чему-то другому ... происходят странные вещи (бесконечность? Действительно ??)
Ну, это чертовски хорошо должно быть выполнимым! Итак, давайте будем больше креативными!
public static bool Test<T>(ref A a, out T t)
{
//...
else
{
if(typeof(T).IsValueType)
{
//... still rubbish here
}
else
{
object o = null;
t = (T)o; //Tricked you stupid compiler!
}
return false;
}
}
Это верно! Это выглядит глупо, незначительное изменение ... Но это компилируется - и для не-значений-типов это работает и дает именно тот результат, который мы хотим! Если T является ref-типом, он инициализируется как ноль.
Все еще проблема с типами значений. Несколько неохотно творчество обращает его внимание на рефлексию. После некоторого случайного поиска в рефлексии, поиска чего-то стоящего (и нет! Вы не можете получить конструктор для типа значения, он возвращает ноль) Я наткнулся на небольшую заметку по msdn:
"Чтобы создать экземпляр типа значения, у которого нет экземпляра
конструкторы, используйте метод CreateInstance. "
Введите CreateInstance<T>()
- http://msdn.microsoft.com/en-us/library/0hcyx2kd.aspx.
"Универсальный метод CreateInstance используется компиляторами для реализации
создание экземпляров типов, указанных параметрами типа. "
Теперь мы куда-то добираемся! Конечно, это говорит
"В общем случае CreateInstance не используется в приложении
код, потому что тип должен быть известен во время компиляции. Если тип
известный во время компиляции, можно использовать нормальный синтаксис
оператор в C #, новый в Visual Basic, gcnew в C ++). "
Но эй - мы не совсем занимаемся обычными делами, мы находимся в креативном режиме, и компилятор раздражительно относится к нам. Полностью оправдывает попытку.
public static bool Test<T>(ref A a, out T t)
{
//...
else
{
if(typeof(T).IsValueType)
{
t = Activator.CreateInstance<T>(); //Probably not your everyday code...
}
else
{
object o = null;
t = (T)o;
}
return false;
}
}
И БАМ! Вот и все! Это полностью работает Су хорошо!
Ниже приведен код, протестированный и запущенный как в VS2010SP1, так и в MonoDevelop (с Unity3.4)
с использованием системы;
namespace GenericUnrestrictedOutParam
{
class Program
{
class TestClass
{
public int i;
}
struct TestStruct
{
int i;
TestClass thing;
};
public static void NullWorkaround<T>(out T anything)
{
if (typeof(T).IsValueType)
{
anything = Activator.CreateInstance<T>();
}
else
{
object o = null;
anything = (T)o;
}
}
static void Main(string[] args)
{
int i;
float f;
string s;
TestStruct ts;
TestClass c;
NullWorkaround<int>(out i);
NullWorkaround<float>(out f);
NullWorkaround<string>(out s);
NullWorkaround<TestStruct>(out ts);
NullWorkaround<TestClass>(out c);
} //Breakpoint here for value-checking
}
}
И великолепный «вывод» (из localals-panel @ breakpoint):
args {string[0]} string[]
i 0 int
f 0.0 float
s null string
- ts {GenericUnrestrictedOutParam.Program.TestStruct} GenericUnrestrictedOutParam.Program.TestStruct
i 0 int
thing null GenericUnrestrictedOutParam.Program.TestClass
c null GenericUnrestrictedOutParam.Program.TestClass
Даже структура со значением и типом класса в нем прекрасно обрабатывается: тип значения равен 0, экземпляр класса равен нулю.
Миссия выполнена!