3

I can't manage to write a VB.NET extension method which can be called from an object of type ListOfTests in the below solution.

I can write the extension method in C# using generic type constraints.

Here's some test code I've writen to demonstrate the problem which I have encountered in a much larger existing VB.NET solution.

The extension method in question here is FilterByIDs, which is just an abritrary example I chose.

Working C# solution:

using System;
using System.Collections.Generic;
using System.Linq;

static class TestApp
{
    public static void Main()
    {
        var objTest = new ListOfTests
        {
            new Test {Id = 1}, new Test {Id = 2}, new Test {Id = 3}
        };

        objTest.FilterByIDs(new [] {3});

        // Prints "3" only - Extension method works
        objTest.ForEach(x => Console.WriteLine(x.Id));
    }
}

public static class Extensions
{
    public static void FilterByIDs<T, TID>(this List<T> target, IEnumerable<TID> objIDs) where T : ITest<TID>
    {
        target.RemoveAll(x => !objIDs.Contains(x.Id));
    }
}

public class ListOfTests : List<Test>
{}


public class Test : ITest<int>
{
    public int Id { get; set; }
}

public interface ITest<TID>
{
    TID Id { get; }
}

My attempt at writing this in VB.NET (Does not build):

Imports System.Runtime.CompilerServices

Class TestApp
    Public Shared Sub Main()
        Dim objTest As New ListOfTests()
        objTest.Add(New Test() With { .ID = 1})
        objTest.Add(New Test() With { .ID = 2})
        objTest.Add(New Test() With { .ID = 3})

        objTest.FilterByIDs({3})

        objTest.ForEach(Sub(x) Console.WriteLine(x.ID))
    End Sub
End Class

Public Module Extensions
    <Extension>
    Public Sub FilterByIDs(Of T As ITest(Of TID), TID)(target As List(Of T), objIDs As IEnumerable(Of TID))
        target.RemoveAll(Function(x) Not objIDs.Contains(x.Id))
    End Sub
End Module

Public Class ListOfTests
    Inherits List(Of Test)
End Class

Public Class Test
    Implements ITest(Of Integer)

    Public Property ID As Integer Implements ITest(Of Integer).ID
End Class

Public Interface ITest (Of T)

    Property ID As T
End Interface

Build error from the VB.NET solution is

" TestApp.vb(18, 16): [BC36561] Extension method 'FilterByIDs' has type constraints that can never be satisfied."

Is it possible to write this in VB.NET?

user2769442
  • 113
  • 1
  • 1
  • 4
  • Almost exactly the situation from the [documentation](https://learn.microsoft.com/en-us/dotnet/visual-basic/misc/bc36561). *"To correct this error: Change the type declaration to remove the interdependence between the types."* – GSerg Jun 26 '19 at 15:40
  • How can we do that in this case? – user2769442 Jun 26 '19 at 15:46
  • One way is to have `Public Class ListOfTests : Inherits List(Of ITest(Of Integer))` and `Public Sub FilterByIDs(Of TID)(ByVal this As List(Of ITest(Of TID)), ByVal objIDs As IEnumerable(Of TID))`. – GSerg Jun 26 '19 at 15:48
  • Thanks for the suggestion, unfortunately I can't do that, there is (a lot) of code elsewhere in the problematic solution that would break from changing the ```Inherits List(Of Test)``` - I.e Code that uses instance specific functionality that is not on the Interface – user2769442 Jun 26 '19 at 15:51
  • That is probably another reason why inheriting from `List` is [discouraged](https://stackoverflow.com/q/21692193/11683). – GSerg Jun 26 '19 at 15:56
  • I don't disagree! I've inherited a huge in-production solution that unfortunately relies on the idea. Said solution currently has over 100 ```FilterByIDs``` functions (along with others) that are duplicated identically :( I'm not sure there's much I can do about it. – user2769442 Jun 26 '19 at 16:03
  • Convert them to non-extension functions? Compile the extension function in its working C# form into a separate library, reference it from VB and see if you can now delete the VB declarations? – GSerg Jun 26 '19 at 16:19
  • What happens if you make the filter function only have a single type argument `TID` and make `Target` be either `List(Of ITest(Of TID))` or `IEnumerable(Of ITest(Of TID))`? The latter might be necessary because I don't think the type argument for `List` has any variance whereas the type argument for `IEnumerable(Of T)` is covariant. – Craig Jun 27 '19 at 13:53

1 Answers1

1

Not the same elegance and compile safety, but working:

<Extension>
Public Module Extensions

    <Extension>
    Public Sub FilterByIDs(Of T)(target As IList, objIDs As IEnumerable(Of T))
        If (target Is Nothing) Then Throw New NullReferenceException() 'To simulate instance method behavior
        For i As Int32 = target.Count - 1 To 0 Step -1
            Dim myTest As ITest(Of T) = TryCast(target(i), ITest(Of T))
            If (myTest Is Nothing) Then
                target.RemoveAt(i)
            Else
                If (Not objIDs.Contains(myTest.ID)) Then target.RemoveAt(i)
            End If
        Next
    End Sub

End Module
Christoph
  • 3,322
  • 2
  • 19
  • 28