probem with generics  
Author Message
Lehmberg





PostPosted: Visual C# Language, probem with generics Top

Hi,

I've written the the code below:

static public void Test<T>(T arg)

{

write(arg);

}

static public void write(char c)

{

Console.WriteLine("char");

}

static public void write(int c)

{

Console.WriteLine("int");

}

It should be possible to write like this: Test('c'); or Test(10) and the appropiate write methods should be called. When I try to compile it I get two errors:

Error 1 The best overloaded method match for 'tester.Program.write(char)' has some invalid arguments.

Error 2 Argument '1': cannot convert from 'T' to 'char'.

Is there another way to make the templated method call the appropiate methods. It seems to me that writing something like this in c++ would be no problem

Thanks,

Alex Lehmberg



Visual C#11  
 
 
Mattias Sjogren





PostPosted: Visual C# Language, probem with generics Top

Right, but generics are different from templates. Overload resolution happens at compile time, but the generic method "instantiation" happens at runtime. Thats why this wont work.



 
 
NewWorldMan





PostPosted: Visual C# Language, probem with generics Top

Try one method like this:

static public void Test<T>(T arg)

{

write(arg);

}

Then call...

Test("c");
Test(2);



 
 
NewWorldMan





PostPosted: Visual C# Language, probem with generics Top

Sorry I meant

static public void write<T>(T c)

{

Console.WriteLine(c);

}


 
 
Mark Rendle





PostPosted: Visual C# Language, probem with generics Top

For the very specific example you've given, this code would work:



 static void Main(string[] args)
        {
            try
            {
                Test(2);
                Test('c');
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            Console.ReadKey();
        }

        static void Test<T>(T arg)
        {
            if(arg.GetType() == typeof(int))
            {
                Write(int.Parse(arg.ToString()));
            }
            else
            {
                Write(char.Parse(arg.ToString()));
            }
        }

        static void Write(char c)
        {
            Console.WriteLine(c);
        }

        static void Write(int i)
        {
            Console.WriteLine(i);
        }

 

I don't know how useful that will be in your real-world issue, sorry.



 
 
James Curran





PostPosted: Visual C# Language, probem with generics Top

EEk...that offend my nature, although I can't see a better way.

One suggestion I'd make is to change the if () to:

if(typeof(T) == typeof(int))

I think the would have a slightly better chance of the JITter realizing that the condition is a constant and just rendering the needed portion.



 
 
Mark Rendle





PostPosted: Visual C# Language, probem with generics Top

That's odd. I'm sure I tried a test app with typeof(T) and got a compile error, but I just checked to make sure and it's fine, so yes, you're absolutely right, that's the better way.

 
 
Peca55





PostPosted: Visual C# Language, probem with generics Top

Hi James

Doing the code snippet and C# compiler seems to be cute (JIT don't do anything magical). Following is result comparing IL code with WinDiff:

//000027:          {

//000028:              if ( typeof(T) == typeof( int ) )

//000028:              if(arg.GetType() == typeof(int))

    IL_0000:   /* D0    | (1B)000001        */ ldtoken     !!T/*1B000001*/

    IL_0005:   /* 28    | (0A)000013        */ call        class [mscorlib/*23000001*/]System.Type/*01000015*/ [mscorlib/*23000001*/]System.Type/*01000015*/::GetTypeFromHandle(valuetype [mscorlib/*23000001*/]System.RuntimeTypeHandle/*01000016*/) /* 0A000013 */

    IL_000a:   /* D0    | (01)000017        */ ldtoken     [mscorlib/*23000001*/]System.Int32/*01000017*/

    IL_000f:   /* 28    | (0A)000013        */ call        class [mscorlib/*23000001*/]System.Type/*01000015*/ [mscorlib/*23000001*/]System.Type/*01000015*/::GetTypeFromHandle(valuetype [mscorlib/*23000001*/]System.RuntimeTypeHandle/*01000016*/) /* 0A000013 */

    IL_0014:   /* 33    | 18                */ bne.un.s    IL_002e

    IL_0000:   /* 0F    | 00                */ ldarga.s    arg

    IL_0002:   /* FE16 | (1B)000001        */ constrained. !!T/*1B000001*/

    IL_0008:   /* 6F    | (0A)000013        */ callvirt    instance class [mscorlib/*23000001*/]System.Type/*01000015*/ [mscorlib/*23000001*/]System.Object/*01000001*/::GetType() /* 0A000013 */

    IL_000d:   /* D0    | (01)000016        */ ldtoken     [mscorlib/*23000001*/]System.Int32/*01000016*/

    IL_0012:   /* 28    | (0A)000014        */ call        class [mscorlib/*23000001*/]System.Type/*01000015*/ [mscorlib/*23000001*/]System.Type/*01000015*/::GetTypeFromHandle(valuetype [mscorlib/*23000001*/]System.RuntimeTypeHandle/*01000017*/) /* 0A000014 */

    IL_0017:   /* 33    | 18                */ bne.un.s    IL_0031

We can always argue, whitch code is better.

Peca



 
 
James Curran





PostPosted: Visual C# Language, probem with generics Top

Well, on pure size alone, mine wins by 3 bytes (0x15 vs 0x18, or 21 vs 24), and we can see that the last 11 bytes (cooresponding to "== typeof(int)") are identical.

The key difference is that my version calls System.Type.GetTypeFromHandle(), while the other method calls the virtual function GetType(). Both are direct calls to nonmanaged code, so it's hard to say which woul dbe faster, although GetType has the overhead of a virtual function call. (Personally, I'd expect GetType to call GetTypeFromHandle() internall, but I have no way of proving that)



 
 
Peca55





PostPosted: Visual C# Language, probem with generics Top

Yes James I agree.

We should use mscorlib de**** to verify situation. If I remember correctly, the 'call' always do null pointer check, 'callvirt' don't do it -> maybe we are in a stalemate situation or we need to do brute force timed loop test to evaluate, which one code fragment is cheaper!

Peca



 
 
Mattias Sjogren





PostPosted: Visual C# Language, probem with generics Top

It's the other way around. callvirt throws if you specify a null pointer, call does not. That's why the C# compiler almost always emits callvirt even when calling non-virtual methods.



 
 
Peca55





PostPosted: Visual C# Language, probem with generics Top

Thank you Mattias for your correction. I should remember it now forever. I listened some webcast from Richard S** (a .NET main developer whose surname I can't remember for now). I need to look that particular presentation again.

Peca



 
 
Peca55





PostPosted: Visual C# Language, probem with generics Top

Some thought about your 3 bytes shorter code. the longer code has constrained. prefix for a good reason:

Quote from standard: " Boxing and unboxing of generic arguments adds performance overhead to a CLI implementation. The constrained. prefix  can improve performance during virtual dispatch to a method defined by a value type, by avoiding boxing the value type."



 
 
James Curran





PostPosted: Visual C# Language, probem with generics Top

The constrainted is needed in that case, because we are passing a "T arg"--- a data item of unknown type, which might be a reference or value type.

In my code, it is passing a known value type (an internal handle), so it won't be boxed anyway.