Q:

Use a delegate to assess equality

I have built my own generic class to implement IEqualityComparer(Of T), which takes a delegate as an input for the constructor. This delegate takes two objects of type T as an input and returns True if they are to be equal.
''' <summary>
''' A generic equality comparer which assess the equality of two objects via the provided delegate. 
''' </summary>
''' <typeparam name="T">The type of the objects to compare.</typeparam>
Public Class GenericEqualityComparer(Of T)
    Implements IEqualityComparer(Of T)

    ''' <summary>
    ''' The delegate invoked to verify equality between two given objects of type <see cref="T"/>.
    ''' </summary>
    Private Comparer As Func(Of T, T, Boolean)

    Private Sub New()
        Throw New NotImplementedException("User other constructor.")
    End Sub

    Public Sub New(Comparer As Func(Of T, T, Boolean))
        Me.Comparer = Comparer
    End Sub

    Public Shadows Function Equals(x As T, y As T) As Boolean Implements IEqualityComparer(Of T).Equals
        If x Is Nothing OrElse y Is Nothing Then
            Return False
        Else
            Return Comparer.Invoke(x, y)
        End If
    End Function

    Public Shadows Function GetHashCode(obj As T) As Integer Implements IEqualityComparer(Of T).GetHashCode
        Return obj.GetHashCode
    End Function

End Class

A example of usage would be
Dim Comparer as New GenericEqualityComparer(Of Person)(function(p, q) p.Name = q.Name And p.Surname = q.Surname)

My concern is over the implementation of GetHashCode(obj As T), which should somehow account for what the delegate focuses on.
Am I right to be concerned? Any ideas for a fix?

So far I was thinking of replacing the delegate signature by Func(T, IList(Of String)): the delegate would be invoked on both objects X and Y, then the resulting values would be stored in their own list, then both list would be compared side by side: if all side-by-side comparisons are correct, then Equals returns True. GetHashCode would run a .GetHashCode on the list of strings returned by the same delegate.
''' <summary>
''' A generic equality comparer which assess the equality of two objects by comparing the projection of their values via a delegate. 
''' </summary>
''' <typeparam name="T">The type of the objects to compare.</typeparam>
Public Class ProjectionEqualityComparer(Of T)
    Implements IEqualityComparer(Of T)

    ''' <summary>
    ''' The delegate invoked to project an object of type <see cref="T"/> onto a list of strings. Each string item correponds to the projection of a particular state or property of the object.
    ''' </summary>
    Private Projector As Func(Of T, IList(Of String))

    Private Sub New()
        Throw New NotImplementedException("User other constructor.")
    End Sub

    Public Sub New(Projector As Func(Of T, IList(Of String)))
        Me.Projector = Projector
    End Sub

    Public Shadows Function Equals(x As T, y As T) As Boolean Implements IEqualityComparer(Of T).Equals
        If x Is Nothing OrElse y Is Nothing Then
            Return False
        Else

            Dim xList As IList(Of String) = Me.Projector.Invoke(x)
            Dim yList As IList(Of String) = Me.Projector.Invoke(y)

            Dim xEnum As IEnumerator(Of String) = xList.GetEnumerator
            Dim yEnum As IEnumerator(Of String) = yList.GetEnumerator
            Do While xEnum.MoveNext = True And yEnum.MoveNext = True
                If xEnum.Current <> yEnum.Current Then Return False
            Loop

            Return True

        End If
    End Function

    Public Shadows Function GetHashCode(obj As T) As Integer Implements IEqualityComparer(Of T).GetHashCode
        Return Me.Projector.Invoke(obj).GetHashCode
    End Function

End Class

A usage would be
Dim OtherComparer as New ProjectionEqualityComparer(Of Person)(function(p) New List(of String) From {p.Name, p.Surname}

This solution is not so satisfying because it adds a level of complexity for anyone who would review it. It is also hard to turn into a metaphor, hence making the documentation not so user-friendly.

Having originally posted the question on SO, @pstrjds suggested to define a HashCode delegate instead of the Comparer or Projector delegates.
I like it, but my understanding is that GetHashCode would accept some collisions, whilst Equals should avoid at all cost. Correct?
Source: https://docs.microsoft.com/en-us/dotnet/api/system.object.gethashcode?view=netframework-4.8

Do not test for equality of hash codes to determine whether two objects are equal. (Unequal objects can have identical hash codes.) To test for equality, call the ReferenceEquals or Equals method. 

A:

GetHashCode will mostly be used in collections using hash tables internally, like dictionaries or hash sets. If you know that you are only going to test for equality, it is safe to not implement GetHashCode.

Instead of letting the projector return a list of values, better store a list of accessor delegates. If you create a list of values, this list will have to be created each time you are comparing values. Accessors are created once.
Private Sub New() cannot be called from outside the class. No need to throw an exception. You can even drop it completely, because when you provide any other constructor with parameters, the default constructor (i.e. the constructor without parameters) will not be generated by VB.
Calling GetHashCode on a list, only gets a hash code for the list object, without considering the list entries. We must calculate a combined hash code ourselves.
Our comparer will be more generic, if we don't assume the values to be of type String and instead operate on Object.

My suggestion:
Public Class ValuesEqualityComparer(Of T)
    Implements IEqualityComparer(Of T)

    ReadOnly _valueAcessors As IList(Of Func(Of T, Object))

    ' We can drop the parameter-less constructor. The other one replaces this one. 
    ' Now, you must provide a an argument for New.
    'Private Sub New()
    'End Sub

    Public Sub New(valueAcessors As IList(Of Func(Of T, Object)))
        _valueAcessors = valueAcessors
    End Sub

    Public Overloads Function Equals(x As T, y As T) As Boolean _
        Implements IEqualityComparer(Of T).Equals

        Return _valueAcessors.All(Function(v) Object.Equals(v(x), v(y)))
    End Function

    Public Overloads Function GetHashCode(obj As T) As Integer _
        Implements IEqualityComparer(Of T).GetHashCode

        Dim hash As Integer = 17
        For Each v In _valueAcessors
            hash = hash * 23 + If(v(obj), 0).GetHashCode()
        Next
        Return hash
    End Function
End Class

You would create a comparer like this
Dim comparer = New ValuesEqualityComparer(Of TestClass)(
    {Function(x) x.Id,
     Function(x) x.Name})

Note, IList is compatible with arrays and List(Of T). I am using an array literal here. See also: How to: Initialize an Array Variable in Visual Basic

