CodeConverter icon indicating copy to clipboard operation
CodeConverter copied to clipboard

VB -> C#: Field initializers referencing non-static fields

Open mrmonday opened this issue 5 years ago • 12 comments

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.

mrmonday avatar Apr 16 '19 08:04 mrmonday

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.

GrahamTheCoder avatar Apr 16 '19 08:04 GrahamTheCoder

This also needs to handle methods with ByRef parameters, see #307.

mrmonday avatar May 05 '19 18:05 mrmonday

@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";
    }
}

tverweij avatar Sep 03 '19 14:09 tverweij

Thanks, that sounds quite straightforward to implement. So when there's a base class it's presumably:

  1. Base fields (in declaration order)
  2. Base constructor
  3. This fields (in declaration order)
  4. This constructor

GrahamTheCoder avatar Sep 03 '19 15:09 GrahamTheCoder

Exactly

tverweij avatar Sep 03 '19 15:09 tverweij

OK so in C# it's:

  1. Base fields (in declaration order)
  2. This fields (in declaration order)
  3. Base constructor
  4. 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).

GrahamTheCoder avatar Sep 03 '19 15:09 GrahamTheCoder

I just did some test.

The result is:

  1. 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
  2. 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)
  3. This constructor

tverweij avatar Sep 03 '19 15:09 tverweij

Correction:

2: The static fields are initialized in a Static constructor - if there is a static constructor, this follows the same rules.

tverweij avatar Sep 03 '19 15:09 tverweij

But it looks like the initialization of the fields and properties that are defined in different partial classes are in random order.

tverweij avatar Sep 03 '19 16:09 tverweij

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

GrahamTheCoder avatar Mar 19 '20 15:03 GrahamTheCoder

The same problem exists for property initializers. Was this supposed to be fixed too or should I create a new issue for that?

Sympatron avatar Oct 18 '23 17:10 Sympatron

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

GrahamTheCoder avatar Oct 19 '23 09:10 GrahamTheCoder