tags:

views:

415

answers:

7

Hey,

I'm trying to write a method like this:

public static T Test<T>()
{
  if (typeof(T)==typeof(string))
     return "1241";

  // do something else
}

but I can't seem to figure out how to pull it off. I want to return values depending on the type of T that the method was invoked with. I need to return strings, int's, custom classes, List etc.

The actual usecase is some custom serialization code where it is essential that the deserializing code knows the type of the object it should produce.

Clarification: the example above gives the following error: Cannot convert string to type T

The ideal solution would work on value types and reference types, and would not include a dummy parameter for overload resolution.

I'm starting to doubt if that ideal solution exists though.

Thanks, Lucas

+6  A: 

You have to cast the return value to T, e.g. something like this for reference types:

public static T Test<T>() where T : class
{
  if (typeof(T)==typeof(string))
     return "1241" as T;

  return default(T);
}
M4N
Compiler says: Cannot convert type 'string' to T
Patrick McDonald
Sorry, I updated the sample. Note the "where T : class" part.
M4N
only problem now is T must be a class, so will not work for int, long, etc.
Patrick McDonald
cant you do... where T: class, struct
Chalkey
@Chalkey: No, you can't. The compiler will complain that "The 'class' or 'struct' constraint must come before any other constraints".
LukeH
@Patrick: Use Int32 instead of "int" in that case
+5  A: 

Beware! The solution below does not work (verified using the Mono gmcs C# compiler).

However, it should work by my reading of the C# standard, since the overload resolution should favour the non-generic version of the method when possible. The relevant section in ECMA-334 is 25.1.7: “Overloading in generic classes”. Furthermore, Eric Lippert seems to say so, too, in a blog posting.

Feedback would be appreciated: why doesn't this work as expected?


You have got unrelated types and unrelated behaviour: this code screams “use overloading!”

Generics would be appropriate for unrelated types, yet identical (or highly similar) behaviour.

Do this (complete test program to reproduce behaviour):

using System;

class TestClass {
    public static T Test<T>() {
        return TestWith(default(T));
        // do something else
    }

    public static string TestWith(string dummy) {
        // Used only for `string`.
        return "string";
    }

    public static T TestWith<T>(T dummy) {
        // Used for everything else.
        return dummy;
    }

    static void Main() {
        Console.WriteLine("Expected \"0\", got \"{0}\"", Test<int>());
        Console.WriteLine("Expected \"string\", got \"{0}\"", Test<string>());
    }
}

Compiled with gmcs, this yields:

Expected "0", got "0"
Expected "string", got ""

Here, the parameter serves only for the disambiguation of the overloaded call. Explicit generic parameters cannot be used here since one of the functions (the string specialization) isn't generic.

Konrad Rudolph
Test<string> still calls TestWith<T> rather than TestWith(string)
Patrick McDonald
Patrick: damn. true. I was sure I had already used this. Back to the drawing board.
Konrad Rudolph
Sorry, I do not understand what the problem is. Can you provide a program that actually compiles and shows output that you do not expect?
Eric Lippert
@Eric: please see my updated (complete) example. From my reading of the standard, and the passage in your blog posting, I'd expect the specialized `TestWith` to be called instead of the generic one. Clearly, that's not the case so I'm wondering if this is just a compiler error in the Mono compiler or if I didn't understand said passage.
Konrad Rudolph
Got it. The behaviour you're seeing is correct. Overload resolution analysis always happens at compile time, and then the overload that was chosen is "baked in" to the generated generic code. When generic method Test<T>() is compiled, overload resolution does the best it possibly can with the information at its disposal, which is to pick the method that it knows will ALWAYS work -- TestWith<T>. The compiler has no idea that in the future, some caller is going to have T be string. If you want it to pick the best method given _runtime_ information, "dynamic" in C# 4 will do that.
Eric Lippert
This is a pretty common misunderstanding. I'll do a blog post about it on July 6th.
Eric Lippert
Eric, thanks a lot for the explanation. Now I think about how generics are resolved in .NET, it's obvious.
Konrad Rudolph
+2  A: 

Try

public static T Test<T>() where T : class
{
  if (typeof(T) == typeof(string)) return "asdf" as T;  // do something else
  // do something else        
}
Hans Malherbe
Do a hard cast "(T)" instead of using "as T". Then you can remove the where-clause.
Jehof
@Jehof: but the code won't compile anymore. So no, don't do it.
Konrad Rudolph
@Konrad. Of course you are right. This doesn´t work
Jehof
+1  A: 

Can you use ChangeType?

public static T Test<T>()
{
    if (typeof(T)==typeof(string))
        return (T)Convert.ChangeType("1234", typeof(T), CultureInfo.InvariantCulture);
    return default(T);
}
n8wrl
+9  A: 

The intermediate cast to object isn't ideal, but something like this should do the trick:

public static T Test<T>()
{
    if (typeof(T) == typeof(string))
        return (T)(object)"1241";

    // do something else
}
LukeH
Ah crap, I just said that too! :)
leppie
A: 

I found the solution:

public static T Test<T>()
{
  if (typeof(T) == typeof(string))
     return (T)(object)"1241";    // this works.

    // do something else
}

Thanks for all the answers.

Lucas
Please check Luke's answer, he came up with the same solution as you
Patrick McDonald
A: 
public static T Test<T>()
{
  if (typeof(T)==typeof(string))
     return (T)Convert.ChangeType("1241", typeof(T));

  return default(T);
}

I've not tested it though :-)

Preet Sangha