CodeConverter
CodeConverter copied to clipboard
VB -> C#: Field initializers referencing non-static fields
Input code
Public Class A
Private x As Integer = 2
Private y(x) As Integer
End Class
Erroneous output
public class A
{
private int x = 2;
private int[] y = new int[x + 1];
}
Expected output
public class A
{
private int x = 2;
private int[] y;
public A()
{
y = new int[x + 1];
}
}
Details
Product in use: VS extension
Version in use: 6.6.0
Accessing a non-static field in C# is an error, where it allowed in VB (CS0236).
It's a bit more complicated than this, since you need to account for multiple constructors.
Minimum: Add the same initialization to all bottom-level constructors (i.e. which don't pass on to another of the type's constructors). Nice to have: Try to create a new method or constructor to add to bottom level constructors in order to avoid a bit of duplication.
There's another related issue that I haven't looked in to. Which is that I think VB has a different initialization order to C# in terms of constructors and fields. There's this handy article that explains what's going on with C# and why. I don't know what the VB order is, and whether its differences affect this (I'm imagining that a subclass makes use of one of these fields that we've just moved to being initialized at a different time). I can't immediately see a comparison online, just hints in the comments on that article. Anyway, worth a look, but I'd be happy enough with fixing the common cases.
This also needs to handle methods with ByRef parameters, see #307.
@GrahamTheCoder " I don't know what the VB order is"
The VB Initializers run within the constructor before any other constructor code in the sequence top to bottom as they are defined in the class.
So the VB code:
Public Class x
public property a As String =b
private property b As String = "ABC"
Public Sub New()
b = "123"
End Sub
End Class
translate to c# as:
public class x
{
public string a { get; set; };
private string b { get; set; };
public x()
{
//field initializers
a = b //so result of a is null and NOT ABC or 123
b = "ABC"
//end field initializers
//rest of the constructor code
b= "123";
}
}
Thanks, that sounds quite straightforward to implement. So when there's a base class it's presumably:
- Base fields (in declaration order)
- Base constructor
- This fields (in declaration order)
- This constructor
Exactly
OK so in C# it's:
- Base fields (in declaration order)
- This fields (in declaration order)
- Base constructor
- This constructor
This gives the possibility that in VB a field initializer in "this" tries to use something modified in the base constructor. The easiest way to make that work in C# would be for any class that inherits from another, move all its initializers into the constructor in the C# code to emulate what VB did.
We can add a Self Verifying test case for this to be sure of our logic.
Obviously there are then cases you could detect where you can avoid doing this (since it will bloat the code and make it look less like the original).
I just did some test.
The result is:
- If there is no base constructor call, call the parameter-less constructor in the base class (I do not know if that needs any code in c#) - this constructor handles anything by itself
- This fields / property initializers - EXCEPT for static fields /properties - those are initialized at first access (lazy initialization - I do not know if c# can do this)
- This constructor
Correction:
2: The static fields are initialized in a Static constructor - if there is a static constructor, this follows the same rules.
But it looks like the initialization of the fields and properties that are defined in different partial classes are in random order.
I'm consolidating the remainder of #418 here: The reference to a non-static might be inside a lambda, but the initialization it still needs to be moved to the constructor:
Imports System.IO
Public Class Test
Private lambda As System.Delegate = New ErrorEventHandler(Sub(a, b) Len(0))
Private nonShared As System.Delegate = New ErrorEventHandler(AddressOf OnError)
Sub OnError(s As Object, e As ErrorEventArgs)
End Sub
End Class
The same problem exists for property initializers. Was this supposed to be fixed too or should I create a new issue for that?
Ah thanks, looks like I forgot that case despite it being mentioned, I'll reopen this. Probably a small tweak from what's in the original pr: https://github.com/icsharpcode/CodeConverter/commit/a666da3ff3749d24997fd0d2392520fc8644576d#r130394371