F# from C# and other .NET Languages

This section describes how F# data and code can be directly accessed from C# and other .NET languages. This forms part of the specification of the F# language.

In general values can be accessed simply by calling the available values as static methods or using them as static fields. The following example shows F# code being accessed from C#.  Here is the F# code.  The values in bold indicate the functions we're going to be calling from C#:

let rec loop n = 
  if n <= 0 then () else begin
  print_endline (string_of_int n);
  loop (n-1)
  end

  type MyData = A | B of int * MyData

  let rec MyDataPrint d = 
  match d with 
  A -> print_endline "the end!"
  | B (n,d) -> print_endline (string_of_int n); MyDataPrint d


  let rec MyDataMap f d = 
  match d with 
  A -> A
  | B (n,d) -> B (f n,MyDataMap f d)

These types and values can be accessed from the following C# program.  The code in bold show the values above being used from C#.  The code in blue shows how to pass a callback from C# to F#.

class Tester {
  static void Main() { 
  Mydll.loop(10); 
  Mydll.MyData x1 = Mydll.MyData.A();
  Mydll.MyData x2 = Mydll.MyData.B(3,x1);
  Mydll.MyData x3 = Mydll.MyData.B(2,x2);
  Mydll.MyData x4 = Mydll.MyData.B(1,x3);
  Mydll.MyDataPrint(x4);
  Mydll.MyData x5 = Mydll.MyDataMap(FuncConvert.ToFastFunc(new System.Converter(square)), x4);

  Mydll.MyDataPrint(x5);

  if (Mydll.MyData.IsB(x5))
  System.Console.WriteLine("Correct!");

  }

  // If generating F# code that supports generics then just use:
  //     static int square(int x) { return x * x; }
  // Without generics methods used as functional values must 
  // transact objects.

  static object square(object x) { return (int) x * (int) x; }
  }

Concrete Types (Record, Abstract and Discriminated Union Types). Named F# types can be referred to from other .NET languages simply by quoting the appropriate type name. Built-in types list and option use the names Microsoft.FSharp.List and Microsoft.FSharp.Option.

Generic Types.Concrete F# types with type parameters must be given appropriate generic arguments if the F# code is compiled to use generics.

Type Abbreviations. F# type abbreviations are eliminated, i.e. are not visible to other .NET languages.

Tuple Types and Function Types. See below

In general values can be accessed simply by calling the available values as static methods or using them as static fields.

This documentation is under construction.

F# data types are compiled to .NET classes with additional support to access the information carried by the data type. Sometimes additional subclasses are generated for discriminants, though these should not be used directly.

Constructing values of F# discriminated unions.

A static method is supported for each discriminant, e.g.

  using Microsoft.FSharp;
  List<int> x = List<int>.Cons(3,List<int>.Nil);
  Option<int> y = Option<int>.Some(3);

Further examples are shown in the worked example above.

Discriminating on F# discriminated unions from C# code

The following section applies if the type has more than one discriminant.

The support depends very marginally on when "null" is used by the F# compiler as a possible representation of value of the data type. This is because types where "null" is used as representation cannot support some instance members (they would result in a null pointer exception when used from C#).

"null" will ONLY be used by the F# compiler in EXACTLY the following situations:

  • For the single value of the "unit" type

  • For discriminated unions marked with the CompilationRepresentation(CompilationRepresentationFlags.PermitNull) attribute where exactly one constructors is nullary - 'null' is then used for as the representation for the nullary constructor. The F# library "option" type is marked with this attribute, but the "list" type is not.

If "null" is NOT used as representation then a type will support

  • A Tag property and several tag_... compile-time constants. These can be used for discrimination by switching.

  • a series of IsXYZ() instance methods, one for each constructor. These can be used for discrimination by predicates.

If "null" IS used as representation then a type will support

  • a GetTag() static method, and several tag_... compile-time constants. These can be used for switching

In the latter case discrimination by predicates can be performed by comparing values to "null", since the null value is then guaranteed to be used for the single nullary constructor of the type.

Thus if the C# value x has type MyType and the F# definition of MyType is:

         type MyType = A of ... | B of ... | C of ...

then the following C# code is valid:

      Console.WriteLine("{0}", x.IsA());
      Console.WriteLine("{0}", x.IsB());
      switch (x.Tag) 
      {
          case MyType.tag_A: 
              Console.WriteLine("A");
              break;
          case MyType.tag_B: 
              Console.WriteLine("B");
              break;
          default:
              Console.WriteLine("Must be a C");
              break;
      }

The compiled form of tuple type names and tuple member names is as follows:

         Tuple<_,_>.Item1
         Tuple<_,_>.Item2

         Tuple<_,_,_>.Item1
         Tuple<_,_,_>.Item2
         Tuple<_,_,_>.Item3

         Tuple<_,_,_,_>.Item1
         Tuple<_,_,_,_>.Item2
         Tuple<_,_,_,_>.Item3
         Tuple<_,_,_,_>.Item4

         ... 

         Tuple<_,_,_,_,_,_,_>.Item1
         ...
         Tuple<_,_,_,_,_,_,_>.Item7

The compiled forms for tuples of size > 7 are under-specified. The above names change slightly when targetting .NET 1.0 or 1.1, i.e. a non-Whidbey release, because we then can't use the arity of the generic type to disambiguate the various "Tuple" names. So the names become Tuple2, Tuple3 etc. to Tuple7.

Data fields compile as properties (not fields) for example:

         type recd = { Name: string; Size: int }

will support

         recd.Name  (a property, not a field)
         recd.Size  (a property, not a field)

A mutable field naturally has a "set" property.

The compiled form of function types is Microsoft.FSharp.FastFunc<A,B>. This will not change, though the details of the implementation of the Microsoft.FSharp.FastFunc class may be revised. FastFunc<A,B> is not a delegate type (which may be what some users expect). This option has been finally rejected for both interoperability and performance grounds.

Invoking functions

Function values can be invoked using the f.Invoke method.

For function types accepting multiple arguments through iterated function types, e.g. int -> int -> int, the the optimized application static methods Microsoft.FSharp.FastFunc.InvokeFast2, Microsoft.FSharp.FastFunc.InvokeFast3 etc. may be used.

Creating function values that accept one argument

It is important to be able to create and use values of type FastFunc from C# in a fashion that is as easy and natural as possible. One option is to create function values by using subclasses of FastFunc<A,B> that override the "Invoke" method. However this is not the recommended way of creating such values, since it is then not possible to use C#'s anonymous delegate feature when creating delegates, and you will have to manually "capture" all variables your function code refers to.

The recommended uniform way to create a FastFunc is to use an anonymous delegate. You simply create an appropriate .NET function-like delegate (e.g. System.Converter) and then call an overload of Microsoft.FSharp.FuncConvert.ToFastFunc. In particular, FuncConvert.ToFastFunc(...) supports the following overloads:

           FuncConvert.ToFastFunc(System.Converter<T,U> f)
                                    producing type FastFunc<T,U> 
           FuncConvert.ToFastFunc(System.Action<T> f)
                                    producing type FastFunc<T,object> 
           FuncConvert.ToFastFunc(System.Predicate<T> f)
                                    producing type FastFunc<T,bool> 

Additionally, there is an implicit conversion from System.Converter<T,U> to FastFunc<T,U>, and thus you can omit the call to FuncConvert.ToFastFunc() but ONLY when reating a delegate of type System.Converter<A,B> (for some A,B).

For example, the following are equivalent:

         List.map<int,string>
            (FuncConvert.ToFastFunc
               ((Converter<int,string>) 
                   delegate(int x) 
                      { return x.ToString() + x.ToString(); }),
                           myList);

and

         List.map<int,string>((Converter<int,string>) delegate(int x) 
                                   { return x.ToString() + x.ToString(); },
                              myList);

Creating curried function values that accept multiple arguments

The above techniques works well when creating F# function values that expect one argument. However the above can be awkward when creating F# function values that accept multiple values, whether by "currying" or by "tupling". Thus the F# library defines additional similar types in order to support additional conversions. In particular it defines

         delegate void System.Action<A1,A2>(A1 x, A2 y);
         delegate void System.Action<A1,A2,A3>(A1 x, A2 y,A3 z);
         delegate B System.Converter<A1,A2,A3,B>(A1 x, A2 y,A3 z);

and Microsoft.FSharp.FuncConvert.ToFastFunc(...) method supports the following overloads that produce "curried" functions:

       ToFastFunc(System.Converter<A1,B> f)
                      producing type 'A1 -> B',
                      i.e. FastFunc<A1,B> 
       ToFastFunc(System.Converter<A1,A2,B> f)
                      producing  'A1 -> A2 -> B',
                      i.e. FastFunc<A1,FastFunc<A2,B>> 
       ToFastFunc(System.Converter<A1,A2,A3,B> f)
                      producing  'A1 -> A2 -> A3 -> B',
                      i.e. FastFunc<A1,FastFunc<A2,FastFunc<A3,B>> >

       ToFastFunc(System.Action<A1> f)
                      producing  'A1 -> unit',
                      i.e. FastFunc<A1,object>
       ToFastFunc(System.Action<A1,A2> f)
                      producing  'A1 -> A2 -> unit',
                      i.e. FastFunc<A1,FastFunc<A2,object>>
       ToFastFunc(System.Action<A1,A2,A3> f)
                      producing  'A1 -> A2 -> A3 -> unit',
                      i.e. FastFunc<A1,FastFunc<A2,FastFunc<A3,object>>>

For example:

      using System;
      using Microsoft.FSharp;
      using Microsoft.FSharp.MLLib;

      List<int> myList = List.of_array(new int[] { 4, 5, 6 });

      string joined = 
        List.fold_right<int,string>
          (FuncConvert.ToFastFunc((Converter<int,string,string>) delegate(int i,string acc) 
                                    { return i.ToString() + "-" + acc; }),
           myList, 
           "");

Creating function values that accept multiple arguments as tuples

To create F# function values that accept their arguments as tuples use Microsoft.FSharp.FuncConvert.ToTupledFunc.

       ToTupledFastFunc(System.Converter<A1,B> f)
                          producing  'A1 -> B',
                          i.e. FastFunc<A1,B> 
       ToTupledFastFunc(System.Converter<A1,A2,B> f)
                          producing  'A1 * A2 -> B',
                          i.e. FastFunc<A1,FastFunc<A2,B>> 
       ToTupledFastFunc(System.Converter<A1,A2,A3,B> f)
                          producing  'A1 * A2 * A3 -> B',
                          i.e. FastFunc<A1,FastFunc<A2,FastFunc<A3,B>> >

       ToTupledFastFunc(System.Action<A1> f)
                          producing  'A1 -> unit',
                          i.e. FastFunc<A1,object>
       ToTupledFastFunc(System.Action<A1,A2> f)
                          producing  'A1 * A2 -> unit',
                          i.e. FastFunc<A1,FastFunc<A2,object>>
       ToTupledFastFunc(System.Action<A1,A2,A3> f)
                          producing  'A1 * A2 * A3 -> unit',
                          i.e. FastFunc<A1,FastFunc<A2,FastFunc<A3,object>>>