diff --git a/.github/scripts/update-testdata-targetframework.ps1 b/.github/scripts/update-testdata-targetframework.ps1
new file mode 100644
index 00000000..67f6ef00
--- /dev/null
+++ b/.github/scripts/update-testdata-targetframework.ps1
@@ -0,0 +1,57 @@
+param()
+
+$ErrorActionPreference = 'Stop'
+
+Write-Host "Creating global.json to enforce .NET 8 for MSBuild"
+$globalJson = '{"sdk":{"version":"8.0.0","rollForward":"latestFeature"}}'
+[System.IO.File]::WriteAllText('global.json', $globalJson, [System.Text.Encoding]::UTF8)
+
+Write-Host "Searching for project files under Tests/TestData..."
+$projFiles = Get-ChildItem -Path Tests/TestData -Recurse -Include *.csproj,*.vbproj,*.fsproj -File -ErrorAction SilentlyContinue
+
+if (-not $projFiles) {
+ Write-Host "No project files found under Tests/TestData"
+ exit 0
+}
+
+$changed = $false
+foreach ($f in $projFiles) {
+ $path = $f.FullName
+ Write-Host "Processing: $path"
+
+ # Use StreamReader to detect encoding and preserve it when writing back
+ $sr = [System.IO.StreamReader]::new($path, $true)
+ try {
+ $content = $sr.ReadToEnd()
+ $encoding = $sr.CurrentEncoding
+ } finally {
+ $sr.Close()
+ }
+
+ # Replace net10.0 and net10.0-windows with net8.0 / net8.0-windows
+ $updated = [System.Text.RegularExpressions.Regex]::Replace($content, 'net10\.0(-windows)?', 'net8.0$1')
+
+ if ($updated -ne $content) {
+ Write-Host "Updating TargetFramework in: $path"
+ # Write back preserving detected encoding and internal newlines
+ [System.IO.File]::WriteAllText($path, $updated, $encoding)
+ $changed = $true
+ }
+}
+
+if ($changed) {
+ Write-Host "Changes detected — committing to local repo so working tree is clean for tests"
+ git config user.name "github-actions[bot]"
+ if ($env:GITHUB_ACTOR) {
+ git config user.email "$($env:GITHUB_ACTOR)@users.noreply.github.com"
+ } else {
+ git config user.email "actions@github.com"
+ }
+ git add -A
+ git commit -m "CI: Update Tests/TestData TargetFramework -> net8.0 for .NET 8 run" || Write-Host "No commit created (maybe no staged changes)"
+ Write-Host "Committed changes locally."
+} else {
+ Write-Host "No TargetFramework updates required."
+}
+
+Write-Host "Done."
diff --git a/.github/workflows/dotnet-tests.yml b/.github/workflows/dotnet-tests.yml
index dee7375f..0fe9bc6f 100644
--- a/.github/workflows/dotnet-tests.yml
+++ b/.github/workflows/dotnet-tests.yml
@@ -19,7 +19,6 @@ jobs:
steps:
- uses: actions/checkout@v4
-
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
@@ -30,11 +29,19 @@ jobs:
with:
vs-version: ${{ inputs.vs-version }}
+ - name: Build
+ run: dotnet build DotNetBuildable.slnf /p:Configuration=Release
+
+ - name: Create global.json to enforce .NET 8 for MSBuild and update Tests/TestData projects
+ if: inputs.dotnet-version == '8.0.x'
+ shell: pwsh
+ run: ./.github/scripts/update-testdata-targetframework.ps1
+
- name: Log MSBuild version
run: msbuild -version
- - name: Build
- run: dotnet build DotNetBuildable.slnf /p:Configuration=Release
+ - name: Log .NET version
+ run: dotnet --info
- name: Execute unit tests
- run: dotnet test Tests/bin/Release/ICSharpCode.CodeConverter.Tests.dll
+ run: dotnet test Tests/Tests.csproj -c Release --framework net${{ inputs.dotnet-version == '8.0.x' && '8.0' || '10.0' }} --no-build
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index c3bada0b..bef5811f 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -7,7 +7,7 @@ on:
branches: [ master, main ]
env:
- BuildVersion: '10.0.0'
+ BuildVersion: '10.0.1'
jobs:
build:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 21cfb598..1622181e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
### C# -> VB
+## [10.0.1] - 2026-02-28
+
+* Reintroduce tentative legacy support for dotnet 8 and VS2022
+* Support slnx format [1195](https://github.com/icsharpcode/CodeConverter/issues/1195)
+
+### VB -> C#
+* Fix for ReDim Preserve of array property - [#1156](https://github.com/icsharpcode/CodeConverter/issues/1156)
+* Fix for with block conversion with null conditional [#1174](https://github.com/icsharpcode/CodeConverter/issues/1174)
+Fixes #1195
+
+
## [10.0.0] - 2026-02-06
* Support for net framework dropped. Please use an older version if you are converting projects that still use it.
diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs
index 6d8eed5e..8f2a4856 100644
--- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs
+++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs
@@ -320,8 +320,18 @@ private async Task> ConvertRedimClauseAsync(VBSyntax
var csTargetArrayExpression = await node.Expression.AcceptAsync(_expressionVisitor);
var convertedBounds = (await CommonConversions.ConvertArrayBoundsAsync(node.ArrayBounds)).Sizes.ToList();
if (preserve && convertedBounds.Count == 1) {
- var argumentList = new[] { csTargetArrayExpression, convertedBounds.Single() }.CreateCsArgList(SyntaxKind.RefKeyword);
+ bool isProperty = _semanticModel.GetSymbolInfo(node.Expression).Symbol?.IsKind(SymbolKind.Property) == true;
+ var arrayToResize = isProperty ? CreateLocalVariableWithUniqueName(node.Expression, "arg" + csTargetArrayExpression.ToString().Split('.').Last(), csTargetArrayExpression) : default;
+ var resizeArg = isProperty ? (ExpressionSyntax)arrayToResize.Reference : csTargetArrayExpression;
+
+ var argumentList = new[] { resizeArg, convertedBounds.Single() }.CreateCsArgList(SyntaxKind.RefKeyword);
var arrayResize = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(Array), nameof(Array.Resize)), argumentList);
+
+ if (isProperty) {
+ var assignment = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, csTargetArrayExpression, arrayToResize.Reference);
+ return SyntaxFactory.List(new StatementSyntax[] { arrayToResize.Declaration, SyntaxFactory.ExpressionStatement(arrayResize), SyntaxFactory.ExpressionStatement(assignment) });
+ }
+
return SingleStatement(arrayResize);
}
var newArrayAssignment = CreateNewArrayAssignment(node.Expression, csTargetArrayExpression, convertedBounds);
diff --git a/CodeConverter/CSharp/NameExpressionNodeVisitor.cs b/CodeConverter/CSharp/NameExpressionNodeVisitor.cs
index f9be5753..1b39251f 100644
--- a/CodeConverter/CSharp/NameExpressionNodeVisitor.cs
+++ b/CodeConverter/CSharp/NameExpressionNodeVisitor.cs
@@ -1,4 +1,4 @@
-using System.Data;
+using System.Data;
using System.Globalization;
using ICSharpCode.CodeConverter.CSharp.Replacements;
using ICSharpCode.CodeConverter.Util.FromRoslyn;
@@ -688,14 +688,19 @@ private static QualifiedNameSyntax Qualify(string qualification, ExpressionSynta
private static bool IsSubPartOfConditionalAccess(VBasic.Syntax.MemberAccessExpressionSyntax node)
{
- var firstPossiblyConditionalAncestor = node.Parent;
- while (firstPossiblyConditionalAncestor != null &&
- firstPossiblyConditionalAncestor.IsKind(VBasic.SyntaxKind.InvocationExpression,
- VBasic.SyntaxKind.SimpleMemberAccessExpression)) {
- firstPossiblyConditionalAncestor = firstPossiblyConditionalAncestor.Parent;
+ static bool IsMemberAccessChain(SyntaxNode exp) =>
+ exp?.IsKind(VBasic.SyntaxKind.InvocationExpression,
+ VBasic.SyntaxKind.SimpleMemberAccessExpression,
+ VBasic.SyntaxKind.ParenthesizedExpression,
+ VBasic.SyntaxKind.ConditionalAccessExpression) == true;
+
+ for (SyntaxNode child = node, parent = node.Parent; IsMemberAccessChain(parent); child = parent, parent = parent.Parent) {
+ if (parent is VBSyntax.ConditionalAccessExpressionSyntax cae && cae.WhenNotNull == child) {
+ return true; // On right hand side of a ?. conditional access
+ }
}
- return firstPossiblyConditionalAncestor?.IsKind(VBasic.SyntaxKind.ConditionalAccessExpression) == true;
+ return false;
}
private static CSharpSyntaxNode ReplaceRightmostIdentifierText(CSharpSyntaxNode expr, SyntaxToken idToken, string overrideIdentifier)
diff --git a/CodeConverter/Common/SolutionFileTextEditor.cs b/CodeConverter/Common/SolutionFileTextEditor.cs
index ba209af3..50981dce 100644
--- a/CodeConverter/Common/SolutionFileTextEditor.cs
+++ b/CodeConverter/Common/SolutionFileTextEditor.cs
@@ -15,9 +15,21 @@ public class SolutionFileTextEditor : ISolutionFileTextEditor
var projectReferenceReplacements = new List<(string Find, string Replace, bool FirstOnly)>();
foreach (var relativeProjPath in relativeProjPaths)
{
- var escapedProjPath = Regex.Escape(relativeProjPath);
- var newProjPath = PathConverter.TogglePathExtension(relativeProjPath);
- projectReferenceReplacements.Add((escapedProjPath, newProjPath, false));
+ // Add replacements for both backslash and forward-slash variants so .slnx files using either separator are handled
+ var nativeVariant = relativeProjPath;
+ var altVariant = relativeProjPath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
+
+ // native (likely backslashes on Windows)
+ var escapedNative = Regex.Escape(nativeVariant);
+ var newNative = PathConverter.TogglePathExtension(nativeVariant);
+ projectReferenceReplacements.Add((escapedNative, newNative, false));
+
+ // alternate (forward slashes)
+ if (altVariant != nativeVariant) {
+ var escapedAlt = Regex.Escape(altVariant);
+ var newAlt = PathConverter.TogglePathExtension(altVariant);
+ projectReferenceReplacements.Add((escapedAlt, newAlt, false));
+ }
}
return projectReferenceReplacements;
diff --git a/Directory.Build.props b/Directory.Build.props
index 5b95257b..c048c3fe 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -8,9 +8,9 @@
4
true
14.0
- 10.0.0.0
- 10.0.0.0
- 10.0.0
+ 10.0.1.0
+ 10.0.1.0
+ 10.0.1
ICSharpCode
Copyright (c) 2017-2023 AlphaSierraPapa for the CodeConverter team
ICSharpCode
diff --git a/Tests/CSharp/StatementTests/MethodStatementTests.cs b/Tests/CSharp/StatementTests/MethodStatementTests.cs
index 39bfd1c7..42c13f4d 100644
--- a/Tests/CSharp/StatementTests/MethodStatementTests.cs
+++ b/Tests/CSharp/StatementTests/MethodStatementTests.cs
@@ -1,4 +1,4 @@
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using ICSharpCode.CodeConverter.Tests.TestRunners;
using Xunit;
@@ -1673,4 +1673,44 @@ public object Func()
}
}");
}
-}
\ No newline at end of file
+
+ [Fact]
+ public async Task WithBlockWithNullConditionalAccessAsync()
+ {
+ await TestConversionVisualBasicToCSharpAsync(@"
+Public Class Class1
+ Public Property x As Class1
+ Public Property Name As String
+End Class
+
+Public Class TestClass
+ Private _Data As Class1
+ Private x As String
+
+ Public Sub TestMethod()
+ With _Data
+ x = .x?.Name
+ End With
+ End Sub
+End Class", @"
+public partial class Class1
+{
+ public Class1 x { get; set; }
+ public string Name { get; set; }
+}
+
+public partial class TestClass
+{
+ private Class1 _Data;
+ private string x;
+
+ public void TestMethod()
+ {
+ {
+ ref var withBlock = ref _Data;
+ x = withBlock.x?.Name;
+ }
+ }
+}");
+ }
+}
diff --git a/Tests/CSharp/StatementTests/RedimPreserveTests.cs b/Tests/CSharp/StatementTests/RedimPreserveTests.cs
new file mode 100644
index 00000000..b54930ed
--- /dev/null
+++ b/Tests/CSharp/StatementTests/RedimPreserveTests.cs
@@ -0,0 +1,33 @@
+using System.Threading.Tasks;
+using ICSharpCode.CodeConverter.Tests.TestRunners;
+using Xunit;
+
+namespace ICSharpCode.CodeConverter.Tests.CSharp.StatementTests;
+
+public class RedimPreserveTests : ConverterTestBase
+{
+ [Fact]
+ public async Task RedimPreserveOnPropertyAsync()
+ {
+ await TestConversionVisualBasicToCSharpAsync(
+ @"Public Class TestClass
+ Public Property NumArray1 As Integer()
+
+ Public Sub New()
+ ReDim Preserve NumArray1(4)
+ End Sub
+End Class", @"using System;
+
+public partial class TestClass
+{
+ public int[] NumArray1 { get; set; }
+
+ public TestClass()
+ {
+ var argNumArray1 = NumArray1;
+ Array.Resize(ref argNumArray1, 5);
+ NumArray1 = argNumArray1;
+ }
+}");
+ }
+}
diff --git a/Tests/LanguageAgnostic/SolutionFileTextEditorTests.cs b/Tests/LanguageAgnostic/SolutionFileTextEditorTests.cs
index 6766e3f2..f502a06a 100644
--- a/Tests/LanguageAgnostic/SolutionFileTextEditorTests.cs
+++ b/Tests/LanguageAgnostic/SolutionFileTextEditorTests.cs
@@ -58,6 +58,31 @@ public void ConvertSolutionFile_WhenInSolutionBaseDirThenUpdated()
Assert.Equal(expectedSlnFile, Utils.HomogenizeEol(convertedSlnFile));
}
+ [Fact]
+ public void ConvertSlnxSolutionFile_ProjectElementsWithForwardSlashesAreUpdated()
+ {
+ //Arrange
+ var slnxContents = "\r\n \r\n";
+ var slnxSln = CreateTestSolution(@"C:\MySolution\MySolution.slnx");
+ var projectId = ProjectId.CreateNewId();
+ var projInfo = ProjectInfo.Create(projectId, VersionStamp.Create(), "ConsoleApp1", "ConsoleApp1",
+ LanguageNames.VisualBasic, @"C:\MySolution\ConsoleApp1\ConsoleApp1.vbproj");
+ slnxSln = slnxSln.AddProject(projInfo);
+ var testProject = slnxSln.GetProject(projectId);
+
+ _fsMock.Setup(mock => mock.File.ReadAllText(It.IsAny())).Returns("");
+
+ var slnConverter = SolutionConverter.CreateFor(new List { testProject },
+ fileSystem: _fsMock.Object, solutionContents: slnxContents);
+
+ //Act
+ var convertedSlnFile = slnConverter.ConvertSolutionFile().ConvertedCode;
+
+ //Assert
+ var expectedSlnFile = "\r\n \r\n";
+ Assert.Equal(expectedSlnFile, Utils.HomogenizeEol(convertedSlnFile));
+ }
+
[Fact]
public void ConvertSolutionFile_WhenInProjectFolderThenUpdated()
{
diff --git a/Tests/TestConstants.cs b/Tests/TestConstants.cs
index 4fa077c9..20bf60b8 100644
--- a/Tests/TestConstants.cs
+++ b/Tests/TestConstants.cs
@@ -19,7 +19,7 @@ public static class TestConstants
public static string GetTestDataDirectory()
{
var assembly = Assembly.GetExecutingAssembly();
- var solutionDir = new FileInfo(new Uri(assembly.Location).LocalPath).Directory?.Parent?.Parent ??
+ var solutionDir = new FileInfo(new Uri(assembly.Location).LocalPath).Directory?.Parent?.Parent?.Parent ??
throw new InvalidOperationException(assembly.Location);
return Path.Combine(solutionDir.FullName, "TestData");
}
diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj
index e1f42328..f25cb7b9 100644
--- a/Tests/Tests.csproj
+++ b/Tests/Tests.csproj
@@ -1,6 +1,7 @@
-
+
- net10.0
+ net10.0;net8.0
+ true
Library
ICSharpCode.CodeConverter.Tests
ICSharpCode.CodeConverter.Tests
diff --git a/Vsix/source.extension.vsixmanifest b/Vsix/source.extension.vsixmanifest
index aad6a73e..961647bf 100644
--- a/Vsix/source.extension.vsixmanifest
+++ b/Vsix/source.extension.vsixmanifest
@@ -1,7 +1,7 @@
-
+
Code Converter (VB - C#)
Convert VB.NET to C# and vice versa with this roslyn based converter
https://github.com/icsharpcode/CodeConverter
diff --git a/web/web.esproj b/web/web.esproj
index 7f0a5977..57639216 100644
--- a/web/web.esproj
+++ b/web/web.esproj
@@ -1,5 +1,7 @@
-
+
+ false
+ false
npm run dev
src\
Vitest
@@ -8,5 +10,4 @@
$(MSBuildProjectDirectory)\dist
-
\ No newline at end of file