在C#中使用F#扩展方法

40

如果在使用F#编写的程序集中定义了一些扩展方法和属性,然后在C#中使用该程序集,您是否会在C#中看到所定义的扩展方法和属性呢?

如果是这样的话,那就太酷了!


1
请参阅 http://cs.hubfs.net/forums/thread/13967.aspx。 - Brian
4个回答

55
[<System.Runtime.CompilerServices.Extension>]
module Methods =   
    [<System.Runtime.CompilerServices.Extension>]   
    let Exists(opt : string option) =                
    match opt with
       | Some _ -> true                  
       | None -> false

只需在将要使用该方法的文件中添加命名空间(使用using)即可在C#中使用此方法。

if (p2.Description.Exists()) {   ...}

这里是原始博客文章的链接。

回答评论中的问题“扩展静态方法”:

namespace ExtensionFSharp 

module CollectionExtensions = 

  type System.Linq.Enumerable with   
    static member RangeChar(first:char, last:char) = 
      {first .. last}
在F#中,你可以这样调用:
open System.Linq 
open ExtensionFSharp.CollectionExtensions 

let rangeChar = Enumerable.RangeChar('a', 'z') 
printfn "Contains %i items" rangeChar.CountItems
在 C# 中,你可以这样调用它:
using System;
using System.Collections.Generic;
using ExtensionFSharp;

    class Program
    {
        static void Main(string[] args)
        {
            var method = typeof (CollectionExtensions).GetMethod("Enumerable.RangeChar.2.static");


            var rangeChar = (IEnumerable<char>) method.Invoke(null, new object[] {'a', 'z'});
            foreach (var c in rangeChar)
            {
                Console.WriteLine(c);
            }
        }
    }

现在,把我的奖章给我!


谢谢,但实例扩展属性和静态方法以及静态扩展属性呢?它们也会被调用吗?我问这个问题的原因是因为我知道C#只有实例扩展方法。 - Joan Venge
2
你绝对应该因为通过反射找到了创造性的方法而获得一枚奖牌,也许在2009年这是唯一的方法。然而,现在你可以使用 CompiledNameAttribute 来更简单、类型安全地从 C# 调用 F#。 - Abel

20
尽管我之前的回答不是这样,但我刚刚使用 F# CTP(在 VS shell 上)和来自家里电脑的 C# Express(所有免费开发工具!),并且这可以运行:
F#
#light
namespace MyFSharp

// C# way
[<System.Runtime.CompilerServices.Extension>]
module ExtensionMethods =
    [<System.Runtime.CompilerServices.Extension>]
    let Great(s : System.String) = "Great"

    // F# way
    type System.String with
        member this.Awesome() = "Awesome"
    let example = "foo".Awesome()        

C#

using System;
using MyFSharp;  // reference the F# dll
class Program
{
    static void Main(string[] args)
    {
        var s = "foo";
        //s.Awesome(); // no
        Console.WriteLine(s.Great());  // yes
    }
}

我之前不知道你可以这样做,真妙啊。归功于 @alex。


1
谢谢,但这不只是一个实例扩展方法吗? - Joan Venge
谢谢,我对F#的其他扩展在C#中显示出来更感兴趣,但是你的另一个回复有点针对这个问题。 - Joan Venge

11

由于某些原因,被接受的答案建议使用反射来获取F#类型扩展方法。由于编译后的方法名称在F#版本之间可能不同,也可能因参数、内联和其他命名相关问题而不同,所以我更建议使用CompiledNameAttribute,这样做更容易并且与C#混合得更好。此外,无需反射(及其性能和类型安全问题)。

假设你有以下F#代码:

namespace Foo.Bar
module StringExt =
    type System.String with
        static member ToInteger s = System.Int64.Parse(s)

您无法直接调用它,编译后的版本看起来像这样(根据是否有重载):

namespace Foo.Bar
{
    using Microsoft.FSharp.Core;
    using System;

    [CompilationMapping(SourceConstructFlags.Module)]
    public static class StringExt
    {
        public static long String.ToInteger.Static(string s) => 
            long.Parse(s);
    }
}

除非使用反射,否则无法访问方法String.ToInteger.Static。不过,只需使用CompiledNameAttribute进行简单的方法修饰即可解决该问题:

namespace Foo.Bar
module StringExt =
    type System.String with
        [<CompiledName("ToInteger")>]
        static member ToInteger s = System.Int64.Parse(s)

现在编译好的方法在Reflector中看起来像这样,注意编译后名称的更改:

namespace Foo.Bar
{
    using Microsoft.FSharp.Core;
    using System;

    [CompilationMapping(SourceConstructFlags.Module)]
    public static class StringExt
    {
        [CompilationSourceName("ToInteger")]
        public static long ToInteger(string s) => 
            long.Parse(s);
    }
}

您仍然可以像在F#中一样使用此方法 (例如在此情况下使用 String.ToInteger )。但更重要的是,您现在可以在C#中无需反射或其他诡计就可以使用此方法:

var int x = Foo.Bar.StringExt.ToInteger("123");

当然,你可以在C#中为Foo.Bar.StringExt模块添加一个类型别名来简化生活:

using Ext = Foo.Bar.StringExt;
....
var int x = Ext.ToInteger("123");

这与扩展方法不同,使用System.Runtime.CompilerServices.Extension属性装饰静态成员将被忽略。这只是一种简单的方式,可以从其他 .NET 语言中使用类型扩展。如果你想要一个看起来像作用于类型的“真正的”扩展方法,请使用此处其他答案中的let语法。


6
根据语言规范第10.7节“类型扩展”:
可选扩展成员是静态成员的语法糖。对可选扩展成员的使用会展开为对编码名称的静态成员调用,其中对象作为第一个参数传递。名称的编码在此版本的F#中未指定,并且与C#扩展成员的编码不兼容。

你知道是否有计划统一 F# 和 C# 实现类型扩展的方式吗?如果不需要使用额外的属性就好了。 - Joh
看起来我们在 F# 的第一个版本中不会这样做(例如,在 VS2010 时间范围内不会进行)。 - Brian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接