【问题标题】:How to have a generic out-parameter without any type restrictions如何在没有任何类型限制的情况下拥有通用输出参数
【发布时间】:2012-01-17 17:44:05
【问题描述】:

问题是这样的: 我想要一个具有泛型类型的输出参数的泛型函数。 将泛型类型限制为 ref-type,当然没有问题。 但我想要一个完全不受限制的泛型类型!没有 new() 或类/结构限制!

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);
  }
}

上面的代码说明了我想要做什么。我想设置 out 参数,但只能在与所描述的条件相似的情况下。在Test(...) 的错误情况下,我对out t 的值完全不感兴趣。但上面当然不是工作代码。 上面的问题是out参数必须被初始化。但也许初始化有时很昂贵(取决于T 的类型),我不想初始化一个虚拟类实例只是为了让编译器停止抱怨。那么问题就变成了:你如何初始化一个未知类型(如果它是一个类,确保它被初始化为 null)?

理论上你应该能够写出类似的东西

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 是值类型时,我们应该能够创建它,但编译器不会让我们创建它。第二个问题类似:“它可能是一个值类型!” - 不,我只是确定不是。它应该工作,但它没有。很烦人。

好的。所以我们开始发挥创造力......毕竟,有一个很好的类叫做 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;
  }
}

编译至少。但这仍然是垃圾。运行时错误大量。 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;
  }
}

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

"创建一个没有实例的值类型的实例 构造函数,使用 CreateInstance 方法。”

输入CreateInstance&lt;T&gt;() - http://msdn.microsoft.com/en-us/library/0hcyx2kd.aspx

"CreateInstance泛型方法被编译器用来实现 由类型参数指定的类型的实例化。”

现在我们正在取得进展!当然是说

"一般情况下,应用程序中的 CreateInstance 是没有用的 代码,因为在编译时必须知道类型。如果类型是 在编译时已知,可以使用正常的实例化语法(新 C# 中的运算符,Visual Basic 中的 New,C++ 中的 gcnew)。”

但是,嘿-我们不是在做一般的事情,我们处于创造模式,编译器对我们很生气。完全有理由尝试一下。

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;
  }
}

然后砰!就是这样!效果很好soo! 以下是在 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
    }
}

还有光荣的“输出”(来自 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

即使是带有值和类类型的结构体也处理得很漂亮:值类型为 0,类实例为空。 任务完成!

【问题讨论】:

  • 以防万一有人建议我使用 ref 而不是 out:不,因为我必须在 之前初始化我将它传递给 Test()。完全颠覆了整个想法。
  • TL;DR 中途。看起来你已经解决了自己的问题。虽然可以提出问题并发布答案,但请将您的答案作为答案发布,而不是在问题中。即使它不是一个很好的答案,这也可以工作,并且您正在寻找其他答案。或者如果你灌篮它也可以工作 - 我们并不挑剔:)
  • 您的代码将因您无法分配 0 的任何 struct 而中断 - 您需要 default(T)
  • 对此感到抱歉......好吧,无论如何都有一个真正的答案。我显然只是在为自己做事! ^^ 只是为了学术兴趣。您介意发布一个何时中断的示例吗?

标签: c# generics out restrictions


【解决方案1】:

您的解决方法似乎没有必要——您只需要default(T),它适用于引用类型和值类型:

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

【讨论】:

  • 确实,哈哈,我就知道应该可行……有时候你会忘记最简单的事情^^
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-10
  • 2015-01-13
  • 2021-09-04
  • 1970-01-01
  • 2020-10-14
相关资源
最近更新 更多