VB -> C#: Generic extension method inference widens element types when using a base-type comparer

Open
#1,302 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 3 days

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
55/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
csharp
Domain
devtools, tooling

Research direction

Start from the VB-to-C# conversion path for generic extension-method calls and reproduce the issue with the VB input in the report. Compare the generated Distinct call with the expected output; done means it specifies DerivedValue and preserves the declared return type.

Written by the indexing model from the issue text.

Description

VB -> C#
VB.Net input code
Imports System.Collections.Generic
Imports System.Linq

Public Class BaseValue
    Public Property Key As Integer
End Class

Public Class DerivedValue
    Inherits BaseValue
End Class

Public Module Helpers
    Public Function ToDictionary(values As IEnumerable(Of DerivedValue)) As Dictionary(Of Integer, DerivedValue)
        Return values.Distinct(EqualityComparer(Of BaseValue).Default).ToDictionary(Function(value) value.Key)
    End Function
End Module
Erroneous output

The converted method leaves the generic arguments of Distinct implicit:

public static Dictionary<int, DerivedValue> ToDictionary(IEnumerable<DerivedValue> values)
{
    return values.Distinct(EqualityComparer<BaseValue>.Default).ToDictionary(value => value.Key);
}

C# infers BaseValue as the element type of Distinct, so ToDictionary returns Dictionary<int, BaseValue>.

This produces:

CS0029: Cannot implicitly convert type
'System.Collections.Generic.Dictionary<int, BaseValue>'
to 'System.Collections.Generic.Dictionary<int, DerivedValue>'
Expected output

The converter should preserve the type argument inferred by VB.NET:

public static Dictionary<int, DerivedValue> ToDictionary(IEnumerable<DerivedValue> values)
{
    return values.Distinct<DerivedValue>(EqualityComparer<BaseValue>.Default).ToDictionary(value => value.Key);
}
Details
  • Product: Visual Studio extension, built locally from master.
  • Version: 10.0.1 (local development build).
  • Previous working version: No.

VB.NET and C# use different type inference rules for generic extension methods.

VB.NET first infers the relevant type parameters from the receiver. In this example, it fixes the element type as DerivedValue. The base-type comparer is then accepted through the contravariance of IEqualityComparer(Of T).

C# inference considers both the receiver and the comparer, resulting in BaseValue instead. Omitting explicit type arguments therefore changes the type of the resulting sequence.

Microsoft's VB team describes this receiver-first inference here:
https://devblogs.microsoft.com/vbteam/extension-methods-and-generics-extension-methods-part-5/

A workaround is to specify the type argument explicitly in the VB source:

Return values.Distinct(Of DerivedValue)(EqualityComparer(Of BaseValue).Default).ToDictionary(Function(value) value.Key)
Dominant language
C#
Stars
916
Forks
242
Avg merge
2d 15h
Merged PRs (30d)
9

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from icsharpcode/CodeConverter

All issues in icsharpcode/CodeConverter

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.