Методы расширения F # в С#

Если вы должны были определить некоторые методы расширения, свойства в сборке, написанные в F #, а затем использовать эту сборку в С#, вы увидите определенные расширения в С#?

Если это так, это будет так здорово.

Ответ 1

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

Этот метод может использоваться в С# только путем добавления пространства имен (с использованием) в файл, в котором он будет использоваться.

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

В С# вы вызываете его так:

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);
            }
        }
    }

Теперь дайте мне свою волшебную медаль!

Ответ 2

Несмотря на мой другой ответ, я просто попробовал это с помощью F # CTP (на оболочке VS) и С# 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()        

С#

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.

Ответ 3

В спецификация языка, раздел 10.7 "Расширения типа":

Дополнительные члены расширения - это синтаксический сахар для статических членов. Использование необязательных членов расширения разрабатывается для вызовов статических членов с закодированными именами, где объект передается в качестве первого аргумента. Кодировка имен не указана в этой версии F # и несовместима с кодировками С# членов расширения С#.

Ответ 4

По какой-то причине в принятом ответе предлагается использовать рефлексию для получения метода расширения типа F #. Поскольку имя скомпилированного метода отличается от версий F # и может отличаться в зависимости от аргументов, встраивания и других проблем, связанных с именами, я бы предпочел использовать CompiledNameAttribute, что намного проще и легко сочетается с С#. Кроме того, не требуется никакого отражения (и его проблем с производительностью и типом).

Предположим, что у вас есть это в 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). Но что более важно, теперь вы можете использовать этот метод без отражения или другого обмана с С#:

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

И, конечно, вы можете сделать свою жизнь проще, добавив псевдоним типа в С# для модуля Foo.Bar.StringExt:

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

Это не то же самое, что и метод расширения, и игнорирование элемента static с атрибутом System.Runtime.CompilerServices.Extension. Это просто простой способ использовать типы расширений на других языках .NET. Если вам нужен "подлинный" метод расширения, который, как представляется, действует на тип, используйте let -syntax из других ответов здесь.