2

一篇解释 monads的旧的Yet Another Language Geek 博客文章描述了向 C# 添加 SelectMany 扩展方法,以便将 linq 语法扩展到新类型。

我已经在 C# 中尝试过它并且它有效。我直接转换为 VB.net,但它不起作用。有谁知道 VB.net 是否支持此功能或如何使用它?

这是有效的 C# 代码:

class Identity<T> {
    public readonly T Value;
    public Identity(T value) { this.Value = value; }
}
static class MonadExtension {
    public static Identity<T> ToIdentity<T>(this T value) {
        return new Identity<T>(value);
    }
    public static Identity<V> SelectMany<T, U, V>(this Identity<T> id, Func<T, Identity<U>> k, Func<T, U, V> s) {
        return s(id.Value, k(id.Value).Value).ToIdentity();
    }
}
class Program {
    static void Main(string[] args) {
        var r = from x in 5.ToIdentity()
                from y in 6.ToIdentity()
                select x + y;
    }
}

这是不起作用的VB.net代码(注意:用vs2010编写,因此可能缺少一些续行):

Imports System.Runtime.CompilerServices

Public Class Identity(Of T)
    Public ReadOnly value As T
    Public Sub New(ByVal value As T)
        Me.value = value
    End Sub
End Class
Module MonadExtensions
    <Extension()> _
    Public Function ToIdentity(Of T)(ByVal value As T) As Identity(Of T)
        Return New Identity(Of T)(value)
    End Function
    <Extension()> _
    Public Function SelectMany(Of T, U, V)(ByVal id As Identity(Of T), ByVal k As Func(Of T, Identity(Of U)), ByVal s As Func(Of T, U, V)) As Identity(Of V)
        Return s(id.value, k(id.value).value).ToIdentity()
    End Function
End Module
Public Module MonadTest
    Public Sub Main()
        ''Error: Expression of type 'Identity(Of Integer)' is not queryable.
        Dim r = From x In 5.ToIdentity() _
                From y In 6.ToIdentity() _
                Select x + y
    End Sub
End Module
4

2 回答 2

3

显然 VB.net 要求,除了定义 SelectMany 之外,目标类型还必须实现您想要的方法(例如 Select、Where 等)。

将此方法添加到 Identity 中,程序将编译并运行:

Public Function [Select](Of R)(ByVal projection As Func(Of T, R)) As Identity(Of R)
    Return projection(value).ToIdentity
End Function

您还可以将其实现为“linq-ify”现有类型的扩展方法:

<Extension()> _
Public Function [Select](Of T, R)(ByVal this As Identity(Of T), ByVal projection As Func(Of T, R)) As Identity(Of R)
    Return projection(this.value).ToIdentity
End Function

此外,VB.net 仅在有多个“来自”行时才需要 SelectMany。如果表达式的形式是“从 x 选择 x+1”,那么只需要实现 Identity.Select 方法。

于 2009-09-23T19:01:46.863 回答
2

VB 也应该支持等效代码。确保您正确翻译了扩展方法:

这个 C#:

public static class MonadExtensions
{
    public static Identity<T> ToIdentity<T>(this T value)
    {
        return new Identity<T>(value);
    }
}

会变成这个VB:

Imports System.Runtime.CompilerServices

Module MonadExtensions

  <Extension()> _
  Public Function ToIdentity(Of T)(ByVal value As T) As Identity(Of T)
    Return New Identity(Of T)(value)
  End Function

End Module

更新:您上面的代码是正确的,所以您似乎只是遇到了 VB 编译器的限制。据我所知,根据语言规范,您尝试做的事情是合法的。

但是,我能够通过Identity(Of T)假装实现来欺骗编译器接受查询IEnumerable(Of T)

Public Class Identity(Of T)
  Implements IEnumerable(Of T)
  Public ReadOnly value As T
  Public Sub New(ByVal value As T)
    Me.value = value
  End Sub

  Public Function GetEnumerator() As IEnumerator(Of T) _
    Implements IEnumerable(Of T).GetEnumerator

    Throw New InvalidOperationException("This should never be called.")
  End Function
  Public Function GetEnumerator1() As IEnumerator _
    Implements IEnumerable(Of T).GetEnumerator

    Throw New InvalidOperationException("This should never be called.")
  End Function
End Class

一旦我们说服编译器这是一个有效的查询,它就会正确解析对您的自定义的调用SelectMany

更新2:或者是的,什么斯特兰克你说。我首先尝试过,但显然忘记了该Extension属性。从语言规范来看,如果按照优先顺序,某些东西被认为是可查询的......

  1. 它定义了一个符合条件的 Select 方法。
  2. 它有一个 AsEnumerable() 或 AsQueryable() 方法。
  3. 它有一个 Cast(Of T) 方法。
于 2009-09-23T17:06:50.227 回答