Question

The problem is this: I want a generic function that has an out-parameter of the generic type. Restrict the generic type to ref-type and there is no problem ofcourse. But I wanted a totally unrestricted generic type! No new() or class/struct-restrictions!

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

The above code illustrates what I want to do. I want to set the out parameter, but only under a similar condition as the one depicted. In the false-case of Test(...) I'm totally uninterested in the value of out t. But the above is ofcourse not working code. The problem above is that an out parameter must be initialized. But maybe initialization is sometimes expensive (depends on type of T) and I don't want to initialize a dummy class instance just to make the compiler stop complaining. So the question then becomes: How do you initialize an unknown type (and making sure it is initialized to null if it's a class)??

Well in theory you should be able to write something like

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

But alas it's not so simple. The first problem is when T is value-type, we should be able to create it but compiler wont let us. The second problem is similar: "It could be a value-type!" - no, I just made sure it wasn't. It should work but it doesn't. Very annoying.

Ok. So we start to go creative... after all, there is this nice class called Object, and it has a special relation to all things 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;
  }
}

This compiles atleast. But it's still rubbish. Runtime-error galore. The problem with the value-type is that the object-type remembers what type it really is and when trying to cast to something else... strange things happen (infinity? Really??) Well this damn well should be doable! So lets be more creative!

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

That's right! It looks a stupid insignificant change... But this compiles - and for non-value-types this runs and gives exactly the result we want! If T is a ref-type it gets initilized to null. Still the problem with value-types. Somewhat reluctantly creativity turns it attention to Reflection. After some random digging around on reflection-stuff, looking for something worth trying out (and no! you cant get the constructor for a value-type, it returns null) I stumbled across a small note on msdn:

"To create an instance of a value type that has no instance constructors, use the CreateInstance method."

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

"The CreateInstance generic method is used by compilers to implement the instantiation of types specified by type parameters."

Now we're getting somewhere! Sure it does say

"In general, there is no use for the CreateInstance in application code, because the type must be known at compile time. If the type is known at compile time, normal instantiation syntax can be used (new operator in C#, New in Visual Basic, gcnew in C++)."

But hey - we are not quite doing general stuff, we are in creative-mode, and the compiler is grumpy towards us. Totally justifies giving it a try.

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

AND BAM! That was it! It totally works soo good! Below is some code thats been tested and run in both VS2010SP1 and MonoDevelop (with Unity3.4)

using System;

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

And the glorious "output" (from 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

Even the struct with a value and a class-type in it is beautifully handled: value type is 0, class instance is null. Mission accomplished!

Was it helpful?

Solution

Your workaround seems unnecessary - you just need default(T) which works for both reference and value types:

public static bool Test<T>(ref A a, out T t)
{
  t = default(T);
  return true;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top