Return int in method supposed to return generic class

1.3k views Asked by At

To make it short, I've got a method which look like this

public T GetCookie<T>(string key)
    {
        T obj;

        if (typeof(T) == typeof(int))
        {
            return -1;
        }
        else
        {
            return default(T);
        }
    }

Why am I doing this ? The default value of an int is 0, and i need it to be -1 (I got a huge code sample based on that -1), so i was wondering if something exists that could change the default value of an int, or allow me to return an int instead of my generic class.

I didn't find anything for now :/

3

There are 3 answers

8
xanatos On

Try doing something like:

return (T)(object)-1;
1
Stefan Steinegger On

No, there is no way to change the default value. It is the value that is assigned by the compiler when initializing fields.

A solution for your problem would be purely opinion based.

You could have separate methods if only a limited amount of types are used:

public int GetIntCookie(string key)

public string GetStringCookie<T>(string key)

public DateTime GetDateTimeCookie<T>(string key)

Or you could maintain a dictionary of default values:

private static Dictionary<Type, object> = new Dictionary<Type, object>
{
  { typeof(int), -1 },
  { typeof(string), string.Empty },
  { typeof(DateTime), DateTime.MinValue },
}
public T GetCookie<T>(string key)
{
    object value;
    if (defaultValues.TryGetValue(typeof(T), out value)
    {
      return (T)value;
    }
    return default(T);
}

Or you could keep your implementation (and fix the compiler error)

public T GetCookie<T>(string key)
{
    if (typeof(T) == typeof(int))
    {
        return (T)(object)-1;
    }
    return default(T);
}
3
atlaste On

As I mentioned in the comments, I always prefer a scenario that uses type safety over one that does not. What you would actually like to do is this:

public T GetCookie<T>(string key)
{
    return default(T);
}

public int GetCookie<int>(string key)
{
    return -1;
}

... unfortunately that doesn't work in C# (for very good reasons).

If you need to return a string, the solution from @xanatos is the only right solution IMO.

However, if you don't care to pass along another argument, the next best thing is to use overload resolution to pick the correct member:

class SimpleOverloadTest
{
    private static T GetCookie<T>(string key, T value)
    {
        return value;
    }

    private static int GetCookie(string key, int value)
    {
        return -1;
    }

    static void Main()
    {
        Console.WriteLine(GetCookie("foo", default(int)));
        Console.WriteLine(GetCookie("foo", default(float)));
        Console.ReadLine();
    }
}

Note that this WILL NOT work if you add something like this:

private static T GetCookie<T>(string key)
{
    return GetCookie(key, default(T));
}

The reason for this is that the overload should be determined compile-time, and simply put - in this case, the overload that is picked is the overload with the generic argument -- the wrong one in our case.

Generic specialization

I'll interpret the question from @xanatos as a "why does C# not support generic specializations?". I've read about it in an article from the compiler team a long long time ago, but cannot find it anymore. So I'll do my best from the top of my head... People like Eric Lippert probably can give a much better answer than me about this subject though... Here goes:

Generic types aren't expanded at C# compile time. A method is compiler as a method, with a unique token; you basically call a method token. This is one of the foundations of .NET since the very beginning: you compile code per-method. It also means that overload resolution can happen at 'C# compile time', not JIT time, making the JIT much faster.

The choice of not supporting generic specializations in .NET was I think mainly a choice of (runtime / JIT / compiler) speed.

If we would implement generic overloading, we would have to store multiple method implementations for each method token during C# compile time, which would break the foundation mentioned above. Then, you can compile each set of methods with the same method token to different (assembler) implementations. At this point, you have to realize that this will put a lot of stress on the JIT'ter: the JIT has to perform some sort of overload resolution to pick the 'right' method; the C# compiler can help the JIT'ter with that, because it will simply generate the methods. This alone is already a good reason not to do this - but let's continue:

Ironically, template expansion is basically how it works in the JIT. If you have a value type, the value type is put into the item 'T', and the method is 'really' expanded to a different piece of IL and assembler. In other words: if you have Foo<T>, then Foo<int> will have different IL and assembler code than Foo<double>. This is not the case for reference types, as the same vtable lookup (of the interface constraint) can be made for all calls. In other words, the code is shared for reference types, it's not for value types. You can actually see this happen here.

By doing it like this, your JIT is able to produce very good code very quickly. Since a lot of generics are reference types, it also benefits greatly from the generalization rule, which makes it bleeding fast. However, if you would specialize reference types, it would break this optimization - again, a good reason not to do this.

So, this leaves us with generic specialization on value types alone. At this point, you have to realize that value types can be composed of other value types, which means that we can recursively compose types. In C++ we call these things type-lists (google for: template meta programming), which are expanded compile-time. In C# this would mean (because of the above) that type-lists are expanded during the JIT phase, and probably inlining takes place because value types don't support inheritance. This would put an enormous stress on the JIT compiler, and would also make it much more complex to implement.

To conclude, yes, this was a choice, but I believe it's a well balanced one based on performance and complexity of the JIT if this feature were added.

So we now know why generic specializations aren't supported in .NET. An alternative, much easier, solution is however possible: Just like async and yield don't use continuations, you can also use f.ex. a Dictionary or a dynamic to do the actual overload resolution without breaking anything. This wouldn't break .NET, it would just introduce some helper methods / classes. I guess you can propose it as a feature request of course on MS Connect.