F# extension methods in C#

后端 未结 4 1023
死守一世寂寞
死守一世寂寞 2020-12-02 10:23

If you were to define some extension methods, properties in an assembly written in F#, and then use that assembly in C#, would you see the defined extensions in C#?

相关标签:
4条回答
  • 2020-12-02 11:00

    Despite my other answer, I did just try this with the F# CTP (on VS shell) and C# Express from my box at home (all free dev tools!), and this works:

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

    I was not aware you could do this; nifty. Credit to @alex.

    0 讨论(0)
  • 2020-12-02 11:10

    Per the language spec, section 10.7 "Type extensions":

    Optional extension members are syntactic sugar for static members. Uses of optional extension members elaborate to calls to static members with encoded names where the object is passed as the first argument. The encoding of names is not specified in this release of F# and is not compatible with C# encodings of C# extension members

    0 讨论(0)
  • 2020-12-02 11:16
    [<System.Runtime.CompilerServices.Extension>]
    module Methods =   
        [<System.Runtime.CompilerServices.Extension>]   
        let Exists(opt : string option) =                
        match opt with
           | Some _ -> true                  
           | None -> false
    

    This method could be used in C# only by adding the namespace (using using) to the file where it will be used.

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

    Here is a link to the original blogpost.

    Answering question in comments "Extension Static Methods":

    namespace ExtensionFSharp 
    
    module CollectionExtensions = 
    
      type System.Linq.Enumerable with   
        static member RangeChar(first:char, last:char) = 
          {first .. last}
    

    In F# you call it like so:

    open System.Linq 
    open ExtensionFSharp.CollectionExtensions 
    
    let rangeChar = Enumerable.RangeChar('a', 'z') 
    printfn "Contains %i items" rangeChar.CountItems
    

    In C# you call it like so:

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

    Now, give me my freaking medal!

    0 讨论(0)
  • 2020-12-02 11:20

    For some reason, the accepted answer suggests using reflection to get an F# type extension method. Since the compiled method name is different between versions of F#, and may be different depending on arguments, inlining and other naming related issues, I would rather suggest using CompiledNameAttribute instead, which is much easier and blends in easily with C#. Besides, no reflection (and its performance and type safety issues) necessary.

    Suppose you have this in F#:

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

    You wouldn't be able to call this directly and the compiled version would look like this (depending on whether or not there are overloads):

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

    Unless you would use reflection, you can't access the method String.ToInteger.Static. However, a simple method decoration with the CompiledNameAttribute solves this problem:

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

    Now the compiled method looks like this in Reflector, mark the change in the compiled name:

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

    You can still use this method the way you are used to in F# (as String.ToInteger in this case). But more importantly, you can now use this method without reflection or other trickery from C#:

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

    And of course, you can make your life simpler by adding a type alias in C# for the Foo.Bar.StringExt module:

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

    This is not the same as an extension method, and decorating the static member with a System.Runtime.CompilerServices.Extension attribute gets ignored. This is merely a simple way to use type extensions from other .NET languages. If you want a "genuine" extension method that appears to act on the type, use the let-syntax from the other answers here.

    0 讨论(0)
提交回复
热议问题