Как получить универсальный выходной параметр без каких-либо ограничений типа - PullRequest
2 голосов
/ 11 декабря 2011

Проблема заключается в следующем: Я хочу универсальную функцию, которая имеет выходной параметр универсального типа. Ограничьте универсальный тип до 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, экземпляр класса равен нулю. Миссия выполнена!

1 Ответ

8 голосов
/ 11 декабря 2011

Ваш обходной путь кажется ненужным - вам просто нужно default(T), который работает как для ссылочных, так и для типов значений:

public static bool Test<T>(ref A a, out T t)
{
  t = default(T);
  return true;
}
...