如何在没有任何类型限制的情况下使用通用out参数

Ano*_*ken 2 c# generics out restrictions

问题是:我想要一个泛型函数,它具有泛型类型的out参数.将泛型类型限制为ref-type,并且没有问题.但我想要一个完全不受限制的通用类型!没有new()或类/ 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);
  }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码说明了我想要做的事情.我想设置out参数,但只能在与所描述的条件相似的条件下.在虚假的情况下,Test(...)我对价值完全不感兴趣out t.但上述情况并非工作代码.上面的问题是必须初始化out参数.但也许初始化有时很昂贵(取决于类型)并且我不想初始化一个虚拟类实例只是为了让编译器停止抱怨.那么问题就变成了:如何初始化一个未知类型(如果它是一个类,确保它被初始化为null)?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;
  }
}
Run Code Online (Sandbox Code Playgroud)

但唉,这不是那么简单.第一个问题是当T是值类型时,我们应该能够创建它,但编译器不会让我们.第二个问题是类似的:"它可能是一种价值型!" - 不,我只是确定不是.它应该工作,但事实并非如此.很烦人.

好.所以我们开始发挥创意......毕竟,有一个叫做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;
  }
}
Run Code Online (Sandbox Code Playgroud)

这个编译至少.但它仍然是垃圾.运行时错误很多.value-type的问题在于,object-type会记住它的真实类型以及何时尝试转换为其他东西......奇怪的事情发生了(无限?真的吗?)这个该死的应该是可行的!所以让我们更有创意!

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;
  }
}
Run Code Online (Sandbox Code Playgroud)

那就对了!它看起来是一个愚蠢的微不足道的变化...但是这个编译 - 对于非值类型,这运行给出我们想要的结果!如果T是ref-type,它将被初始化为null.仍然是价值类型的问题.有点不情愿的创造力将注意力转向反思.经过一些随机挖掘反射的东西,寻找值得尝试的东西(并且没有!你不能得到值类型的构造函数,它返回null)我偶然发现了一个关于msdn的小注释:

"要创建没有实例构造函数的值类型的实例,请使用CreateInstance方法."

输入CreateInstance<T>()- http://msdn.microsoft.com/en-us/library/0hcyx2kd.aspx.

"编译器使用CreateInstance泛型方法来实现类型参数指定的类型的实例化."

现在我们到了某个地方!当然可以说

"通常,应用程序代码中的CreateInstance没有用处,因为在编译时必须知道类型.如果在编译时已知类型,则可以使用正常的实例化语法(C#中的新运算符,Visual Basic中的新增功能) ,gcnew in 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;
  }
}
Run Code Online (Sandbox Code Playgroud)

和BAM!就是这样!它太完美了!下面是在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
    }
}
Run Code Online (Sandbox Code Playgroud)

和光荣的"输出"(来自locals-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
Run Code Online (Sandbox Code Playgroud)

即使是带有值和类型类型的结构也能得到很好的处理:值类型为0,类实例为空. 任务完成!

Bro*_*ass 8

您的解决方法似乎是不必要的 - 您只需要default(T)对参考和值类型都有效:

public static bool Test<T>(ref A a, out T t)
{
  t = default(T);
  return true;
}
Run Code Online (Sandbox Code Playgroud)