Click here to Skip to main content
15,879,535 members
Please Sign up or sign in to vote.
4.50/5 (2 votes)
Hi all,
I have found a rather annoying difference between calling generic Extension methods between VB and C#.

I have written some Extension methods in a library written in C#. I am referencing this in a VB program and in a C# program. The difference (and problem) is that calling the function works different in both languages...

Here is the Extension methods signature:
C#
public static TEntityItem AsEntityItem<TEntityItem, TEntity>(this TEntity entity)
         where TEntityItem : EntityItem<TEntity>
         where TEntity : EntityObject
In my case I am creating some extensions to work with Entity Framework, but that doesn't really matter for my question.

In my programs I have an Entity called SalesOrderHeader and an EntityItem<salesorderheader> called SalesOrderItem.
Now when I call this function in VB it looks like follows:
VB
Dim s As New SalesOrderHeader
Dim item = s.AsEntityItem(Of SalesOrderItem)()
This looks pretty neat. The code pretty much reads as plain text. Convert the SalesOrderHeader to a SalesOrderItem.

In C# this looks a little bit different...
C#
SalesOrderHeader s = new SalesOrderHeader();
var item = s.AsEntityItem<OrderHeaderItem, SalesOrderHeader>();
Can you spot the difference? I have to give SalesOrderHeader as a type parameter, while I could leave this out in VB (it isn't even allowed in VB!).

I have a couple of Extension methods like this and the C# way of calling those tends to make the code bloated. Converting between VB and C# becomes impossible...
Is this an uneasy difference between the two languages or am I missing something?

UPDATE:
I have created a small sample project (three actually) for people to copy/paste and check it out.

In my first (C#) project I have created two classes and an Extension method.
They mimic the classes and methods mentioned above.
C#
using System;

namespace WildWildWest
{
	public class Ammo { }

	public class Gun<TAmmo> where TAmmo : Ammo
	{ public TAmmo Ammo { get; set; } }

	public static class Extensions
	{
		public static TGun Load<TGun, TAmmo>(this TAmmo ammo)
			where TGun : Gun<TAmmo>
			where TAmmo : Ammo
		{
			TGun gun = Activator.CreateInstance<TGun>();
			gun.Ammo = ammo;
			return gun;
		}
	}
}
You can simply copy/paste and compile it. Now create a C# project and a VB project and reference the project with the Ammo (Entity in original question), Gun (EntityItem in original question) and the Extension method.

In your C# project you can copy/paste the following code:
C#
using WildWildWest;

namespace CSharpTest
{
	public class BigAmmo : Ammo { }
	public class BigGun : Gun<BigAmmo> { }

	public class Test
	{
		private void SomeMethod()
		{
			BigAmmo ammo = new BigAmmo();
			// Doesn't work. Two type parameters are required.
			//BigGun gunWrong = ammo.Load<BigGun>();
			BigGun gun = ammo.Load<BigGun, BigAmmo>();
		}
	}
}


In your VB project you can copy/paste the following code:
VB
Imports WildWildWest

Public Class BigAmmo
    Inherits Ammo
End Class

Public Class BigGun
    Inherits Gun(Of BigAmmo)
End Class

Public Class Test
    Private Sub SomeMethod()
        Dim ammo As New BigAmmo()
        Dim gun As BigGun = ammo.Load(Of BigGun)()
        ' Doesn't work. Only one type parameter allowed.
        'Dim gunWrong As BigGun = ammo.Load(Of BigGun, BigAmmo)()
    End Sub
End Class

Notice how VB only requires one type parameter where C# requires two?
The type of Ammo could be inferred from the type of Gun, yet C# doesn't.
Unfortunately I am using C# for my current project and my classes are called names like SalesOrderHeader. You can imagine things get pretty bloated when calling order.AsEntityItem<SalesOrderHeaderItem, SalesOrderHeader>()... I can live with a little bloated code though. What I can't live with is that C# appearently can't infer a type even when it's so obvious and VB can.
So, any idea why C# doesn't infer the type of Ammo or how I could make C# infer it anyway?

UPDATE 2:
My last example created some doubts concerning referencing wrong dll's or leaving out code.
So here is the COMPLETE code in C# and VB.
C#
using System;

namespace WildWildWest
{
	public class Ammo { }

	public class Gun<TAmmo> where TAmmo : Ammo
	{ public TAmmo Ammo { get; set; } }

	public static class Extensions
	{
		public static TGun Load<TGun, TAmmo>(this TAmmo ammo)
			where TGun : Gun<TAmmo>
			where TAmmo : Ammo
		{
			TGun gun = Activator.CreateInstance<TGun>();
			gun.Ammo = ammo;
			return gun;
		}
	}
	public class BigAmmo : Ammo { }
	public class BigGun : Gun<BigAmmo> { }

	public class Test
	{
		private void SomeMethod()
		{
			BigAmmo ammo = new BigAmmo();
			// Still does not work...
			//BigGun gunWrong = ammo.Load<BigGun>();
			BigGun gun = ammo.Load<BigGun, BigAmmo>();
		}
	}
}

VB
Public Class Ammo
End Class

Public Class Gun(Of TAmmo As Ammo)
    Public Property Ammo As TAmmo
End Class

Module Extensions
    <System.Runtime.CompilerServices.Extension()>
    Public Function Load(Of TGun As Gun(Of TAmmo), TAmmo As Ammo)(ByVal ammo As TAmmo) As TGun
        Dim gun As TGun = Activator.CreateInstance(Of TGun)()
        gun.Ammo = ammo
        Return gun
    End Function
End Module

Public Class BigAmmo
    Inherits Ammo
End Class

Public Class BigGun
    Inherits Gun(Of BigAmmo)
End Class

Public Class Test
    Private Sub SomeMethod()
        Dim ammo As New BigAmmo()
        ammo.Load(Of BigGun)()
        ' Still does not work...
        'ammo.Load(Of BigGun, BigAmmo)()
    End Sub
End Class
I have also checked the IL and they compile to the same IL. However when checking the compiled VB code and pasting it back it simply does not work because it uses C# notation...
Here's the IL:
.method private instance void  SomeMethod() cil managed
{
  // Code size       16 (0x10)
  .maxstack  1
  .locals init ([0] class ClassLibrary1.BigAmmo ammo)
  IL_0000:  nop
  IL_0001:  newobj     instance void ClassLibrary1.BigAmmo::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  call       !!0 ClassLibrary1.Extensions::Load<class ClassLibrary1.BigGun,class ClassLibrary1.BigAmmo>(!!1)
  IL_000d:  pop
  IL_000e:  nop
  IL_000f:  ret
} // end of method Test::SomeMethod
And here's the VB, gotten using Teleriks JustDecompile.
VB
Public Class Test
    <DebuggerNonUserCode>
    Public Sub New()
        MyBase.New()
    End Sub

    Private Sub SomeMethod()
        Dim ammo As BigAmmo = New BigAmmo()
        ammo.Load(Of BigGun, BigAmmo)()
    End Sub
End Class
Of course ammo.Load(Of BigGun, BigAmmo)() does not work in VB...

Why is it possible for VB to leave out the type of TAmmo? Because TGun is already a type of Gun(Of TAmmo). So the type of TAmmo can be inferred from the type of TGun. In the above example TGun is BigGun(Of BigAmmo), so TAmmo can never be anything else but BigAmmo. VB understands this and says it's okay to just provide the type of TGun. C# does not and still requires me to explicitly type TAmmo...
Posted
Updated 31-Jan-13 20:23pm
v3
Comments
Sergey Alexandrovich Kryukov 30-Jan-13 12:35pm    
How come you have two generic parameters in C# but trying to write just one in instantiation of generic in VB.NET?

I think the difference is only the syntax...
—SA
Sander Rossel 30-Jan-13 13:33pm    
I'm not TRYING to write only one in VB.NET, I'm FORCED to write only one in VB.NET.
Appearently VB.NET can infer the type of TEntity since it's the same as the type of TEntity in TEntityItem.
C#, however, can't infer the type and you have to specify it.
This just struck me as odd since there is no reason why C# can't infer it while VB can.
Not having to type in the type which is already exposed in TEntityItem seems logical to me and gives quite a bit of comfort and cleaner code.
Sergey Alexandrovich Kryukov 30-Jan-13 13:50pm    
You are never forced to use inferences, and, in more complex cases, I would recommend not to use it...
Now, how are you forced? VB.NET uses multiple generic parameters exactly as C#.
—SA
Sander Rossel 30-Jan-13 15:19pm    
VB.NET simply says there's only one type parameter, which is TEntityItem. Whenever I specify the type of TEntity it gives an error saying there is only one type parameter.
In C# I have the exact opposite. I have to specify two and if I only specify one it gives me the error that there is no constructor with only one type parameter.

I find it very confusing that VB.NET and C# work so differently at this point. That's why I thought I might be missing something.
Sergey Alexandrovich Kryukov 30-Jan-13 16:39pm    
I doubt it...
I think C# and VB.NET are structurally and functionally equivalent at this point; and you are missing exactly this...
—SA

1 solution

OK, I know what do to with you. Even if you are were not convinced by my clear example of System.Collection.Generic.Dictionary (you started to argument that the generic parameters are not constrained), I'll better show you a right method of research which you can use by yourself and remove all doubts. You did not provide a comprehensive complete sample, so I'll do it for you.

I'll start with some minimalistic C# samples, with two generic parameters, and with generic parameter constraints:
C#
using System;

namespace MultipleGenericParameters {

    public class SomethingDisposable : System.IDisposable {
        void System.IDisposable.Dispose() { }
    } //class SomethingDisposable

    public class Test {
        public static void GenericMethod<First, Second>(First first, Second second)
            where First: System.IDisposable
            where Second: new() { }
        public static void GenericMethodCaller() {
            GenericMethod<SomethingDisposable, System.Text.StringBuilder>(
                new SomethingDisposable(),
                new System.Text.StringBuilder());
        } //GenericMethodCaller
    } // class Text

    class Program {
        static void Main(string[] args) {
        } //Main
    } //class Program

} //namespace MultipleGenericParameters


Clear enough, isn't it?

For the rest of it, we will need an open-source tool, ILSpy:
http://ilspy.net/[^],
http://en.wikipedia.org/wiki/.NET_Reflector[^].

Get it, open, but better compile by yourself. Reflector, presently commercial, can also be used, if you have this product.

Compile the above code and decompile it to VB.NT. You will get:
VB
Imports System
Imports System.Text

Namespace MultipleGenericParameters

    Public Class SomethingDisposable
        Implements IDisposable
        Sub Dispose() Implements IDisposable.Dispose
        End Sub
    End Class

    Public Class Test
        Public Shared Sub GenericMethod(Of First As IDisposable, Second As New)(first As First, second As Second)
        End Sub

        Public Shared Sub GenericMethodCaller()
            Test.GenericMethod(Of SomethingDisposable, StringBuilder)(New SomethingDisposable(), New StringBuilder())
        End Sub
    End Class

End Namespace


As you can see, two generic parameters with generic parameter constraint fit nicely in VB.NET. Now, you can compile it, add some test code, run it — I bet it works.

You an even "invent" more arguments against VB.NET, but now you have no excuses: you should compile your code, decompile it and make sure it is valid in the different language. Moreover, you can test, and, importantly, decompile it to CIL to IL:

MultipleGenericParameters.Test:
C#
.class public auto ansi beforefieldinit MultipleGenericParameters.Test
	extends [mscorlib]System.Object
{
	// Methods
	.method public hidebysig static 
		void GenericMethod<([mscorlib]System.IDisposable) First, .ctor Second> (
			!!First first,
			!!Second second
		) cil managed 
	{
		// Method begins at RVA 0x205b
		// Code size 2 (0x2)
		.maxstack 8

		IL_0000: nop
		IL_0001: ret
	} // end of method Test::GenericMethod

	.method public hidebysig static 
		void GenericMethodCaller () cil managed 
	{
		// Method begins at RVA 0x205e
		// Code size 18 (0x12)
		.maxstack 8

		IL_0000: nop
		IL_0001: newobj instance void MultipleGenericParameters.SomethingDisposable::.ctor()
		IL_0006: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
		IL_000b: call void MultipleGenericParameters.Test::GenericMethod<class MultipleGenericParameters.SomethingDisposable, class [mscorlib]System.Text.StringBuilder>(!!0, !!1)
		IL_0010: nop
		IL_0011: ret
	} // end of method Test::GenericMethodCaller

	.method public hidebysig specialname rtspecialname 
		instance void .ctor () cil managed 
	{
		// Method begins at RVA 0x2071
		// Code size 7 (0x7)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: call instance void [mscorlib]System.Object::.ctor()
		IL_0006: ret
	} // end of method Test::.ctor

} // end of class MultipleGenericParameters.Test


MultipleGenericParameters.SomethingDisposable:
C#
.class public auto ansi beforefieldinit MultipleGenericParameters.SomethingDisposable
	extends [mscorlib]System.Object
	implements [mscorlib]System.IDisposable
{
	// Methods
	.method private final hidebysig newslot virtual 
		instance void System.IDisposable.Dispose () cil managed 
	{
		.override method instance void [mscorlib]System.IDisposable::Dispose()
		// Method begins at RVA 0x2050
		// Code size 2 (0x2)
		.maxstack 8

		IL_0000: nop
		IL_0001: ret
	} // end of method SomethingDisposable::System.IDisposable.Dispose

	.method public hidebysig specialname rtspecialname 
		instance void .ctor () cil managed 
	{
		// Method begins at RVA 0x2053
		// Code size 7 (0x7)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: call instance void [mscorlib]System.Object::.ctor()
		IL_0006: ret
	} // end of method SomethingDisposable::.ctor

} // end of class MultipleGenericParameters.SomethingDisposable


You can even make a round trip: compiler both VB.NET and C# projects, make sure they compile, decompile into IL. Try to find differences. In this very case, you won't.

Is that finally end of story? If not, take your code and repeat the procedure.

[EDIT #1]

I cannot believe I advocate VB.NET! Yes, this language probably has no chance to become a standard, it has a heavy legacy of some really moronic predecessor "languages" (quotation marks intended), it is hardly taken seriously by serious developers; and I never allow myself to install it on my computers…

But — it's important to be fair and keep to pure facts. Generics in this language is not a failure. :-)

[EDIT #2]

Thank you for clarification. I got your updated, complete code. First of all, C# comments
C#
// Still does not work...
//BigGun gunWrong = ammo.Load<BigGun>();

could rather read "of course it should not compile". :-)

Now, I compiled all your code converted to VB.NET and got as expected:

VB
Imports System
Namespace WildWildWest

   Public Module Extensions
      <System.Runtime.CompilerServices.ExtensionAttribute()>
      Public Function Load(Of TGun As Gun(Of TAmmo), TAmmo As Ammo)
            (ammo As TAmmo) As TGun
         Dim gun As TGun = Activator.CreateInstance(Of TGun)()
         gun.Ammo = ammo
         Return gun
      End Function
   End Module

   Public Class Test
      Private Sub SomeMethod()
         Dim ammo As BigAmmo = New BigAmmo()
         Dim gun As BigGun = ammo.Load(Of BigGun, BigAmmo)()
      End Sub
   End Class

End Namespace


Any problem with that?

Good luck,
—SA
 
Share this answer
 
v4
Comments
Sander Rossel 1-Feb-13 2:29am    
Thanks for your example. Unfortunately it's still not accurate. You have added type constraints, but the contraint on First does not include Second, so Second can never be inferred from First... In my example TGun is always of type Gun(Of TAmmo), so if TGun is BigGun which is a Gun(Of BigAmmo) then TAmmo can never be anything else than BigAmmo. VB understands this, C# does not.
In your example, if First is SomethingDisposable that still says nothing about Second. So both C# and VB cannot infer the type of Second.

I have updated my question with a full C# and VB example and IL (which is the same for both languages, cannot correctly roundtrip to VB).
Sergey Alexandrovich Kryukov 1-Feb-13 2:36am    
No, no... What a minute... I explained to you: no more excuses.

I explained you the method, now it's your turn. Write down your code, whatever you think is accurate. Do the same. Show me the C# code... You never did it so far, but now you can see why. Isn't that fair?

I can say what is unfair: you are using arguments which are not valid in terms of defalsification. This is the idea: no matter what I right, you can always say "it's not accurate". Can you see the point? So, to be fair and constructive, from the standpoint of getting the truth, you should right the C# code disproving the point. Obvious logic...

Now I see, you also need inference. But inference is a language-specific "trick", it does not exist in .NET itself. So, please, write equivalent code without inference. Or use inference, I don't mind. It just does not matter at all. VB.NET will allow you to use two generic parameters anyway...

You write the complete code sample to argue against my statements. In confirm or proof the opposite. Before you do, there is nothing to discuss. OK?

—SA
Sander Rossel 1-Feb-13 4:29am    
I don't understand what you mean. In your example C# and VB work exactly the same and I can't prove differently. However, in my sample, which is pretty different from yours, C# and VB DO work differently yet you say they don't. So far you have not backed up your claims or proven me wrong. VB.NET will NOT allow me to use two generic parameters and C# WILL force me to use two. I have given you my code sample to prove that VB and C# work differently in inferring generic types. I don't really care that the same IL is emitted. I code in C# or VB, not IL.
Sergey Alexandrovich Kryukov 1-Feb-13 10:57am    
What you don't understand? If you think my example is inadequate, test on another one. You never really shown your sample, but if you have it and it compiles, please no prove yourself that the problem really exist, on your sample. I foresee that the problem will disappear when you do it all to the end... Just try.
—SA
Sander Rossel 1-Feb-13 11:05am    
Have you seen my updated question? I have coded an entire sample in VB and C# and shown the differences. It compiles perfectly and you can test it yourself by copy/pasting. Of course you will need VB to test it in VB yourself...
So what I don't understand is why my example isn't good for you and why it should work?

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900