|
F# Manual
- Contents
C# from F# – using C# and the .NET APIs from F#
Assemblies written in C# and other .NET languages can be directly accessed from F#. This makes F# an amazingly powerful language given the huge number of libraries available. This section describes how to use various .NET constructs from F#. A working knowledge of C# or another .NET language is assumed. Longer examples of interoperability can be found in the samples that come with the F# compiler. Types. Types are referenced using the Namespace.Type notation. You may simply use Type if an open Namespace directive has been given. Constructors. Instances of .NET types are constructed using the expression form new Type(arg1,...,argN). See also object expressions. Methods. Instance members are invoked using object.Method(arg1,...,argN). Static methods are invoked using Type.Method(arg1,...,argN). Fields. Instance fields are read using object.Field and written using object.Field <- expr. Static fields are read using Type.Field and written using Type.Field <- expr. Properties. Instance properties are read using object.Property and written using object.Property <- expr. Static properties are read using Type.Property and written using Type.Property <- expr. Void. Methods returning the .NET type void return the F# type unit. Delegates. Delegates values are constructed using new DelegateType(expr) where the expression is an F# function value whose argument and return types match those of the Invoke method of the given delegate type. Delegate types whose Invoke methods returns void should be given a function value with return type unit. Delegate types accepting multiple arguments should be given a function that accepts these arguments in iterated form, e.g., note the two arguments sender and args accepted by a PaintEventHandler are in the form fun sender args -> ... and not fun (sender,args) -> .... open System.Threading;; let t = new Thread(new ThreadStart(fun () -> printf "thread started!\n"));; t.Start();; The new and name of the delegate constructor may be omitted if a lambda expression is used to implement the callback, i.e. open System.Threading;; let t = new Thread(fun () -> printf "thread started!\n");; t.Start();; Events. .NET events (also known as 'listening points' or 'wires') are accessed by using the event as if it were a property, and then callng the Add, AddHandler or RemoveHandler methods on the resulting value. Add receives a function value, the others are more primitive forms receiving delegate values. For example, use menu.Click.Add(fun evArgs -> expr), where expr is an appropriate implementation of a callback. The 'sender' argument associated with all standard .NET events is dropped from the set of arguments passed to the callbacks provided to the Add method. Events may also be used as first-class values of type Microsoft.FSharp.Idioms.IDelegateEvent and Microsoft.FSharp.Idioms.IEvent. open System.Windows.Forms;; let form = new Form();; let guiRefresh g = printf "refresh!\n";; form.Paint.Add(fun e -> guiRefresh e.Graphics);; /// Alternatively use the more primitive AddHandler/RemoveHandler ... let handler = new PaintEventHandler(fun sender e -> guiRefresh e.Graphics);; form.Paint.AddHandler(handler);; form.Paint.RemoveHandler(handler);; F# values that implement .NET interfaces can be generated using object expressions, by defining augmentations on type definitions. Sometimes extra annotations are needed to get a program to typecheck, e.g., rigid constraints using (<expr> : <type>) or flexible constraints using (<expr> :> <type>). In particular extra annotations may be required to help resolve overloading, though also in some situations assocaited with subtyping. Type inference works by a process of strict left-to-right, outer-to-inner type checking, i.e. if extra type information is needed you should add it either to the expression where the information is required itself or one a related expression to the left of the expression. All F# and .NET values are convertible to the System.Object type – in F# this is called obj. Thus F# values can be stored in heterogeneous collections such as System.Collections.ArrayList. Conversions in ther reverse direction are dynamically checked and may raise failures. F# supports the following operators to convert to and from this type:
Upcasts and boxing coercions occur when passing values to functions that accept more general types according to the F# coercion relation. More specifcally, coercions may be applied whenever a coercion constraint is satisfied between a formal and an argument. A boxing coercion will apply only if one F# value is a .NET value type and the coercion target is a .NET reference type (typically System.Object). All values of .NET or F# class or interface types are convertible to the transitive closure of their inherited base types and their interface types. Other F# types (records, discriminated unions and abstract types) are not arranged in a hierarchy and convert only to the type obj and any interface types listed as explicit augmentations. .NET values can also be downcast to types that are in the coercion relation, with possible runtime exceptions. F# supports the following operators to convert .NET values with respect to the coercion relation:
The expression "e :? ty" is equivalent to a dynamic type test. A warning will be emitted if the type of e cannot be statically determined to be a subtype of ty (statically determined == using the inference information available at this point in the typechecking of the program, where inference proceeds left-to-right through the program). An error will be reported if the type test will always succeed. :? patterns are especially useful for testing for classes of .NET exceptions, e.g.,
Valid casts are those between .NET types related by class extension or interface inheritance, and also between F# reference types and the type 'obj' (i.e. System.Object). In F# code, .NET reference types can be null values. Nulls can be checked and created using the following contructs:
Here is an example where we do a null test in a pattern match:
Here is an example where we do a null test in an expression:
At the source language level null is not a valid value for other F# types, and if you attempt to use nullchecking constructs with types such as int or int list then the compiler will complain.] It will also complain if you do null checks or generate null values for a variable type 'a. However note that null will be used as a representation for some F# values, as described in the section on export interopability, though that is largely a choice made by the compiler. On the whole F# code will often assume values .NET reference type values are non-null, so it is good style to eliminate the nullness and make it explicit as early as possible. You should only really need null checks at the API boundary. Strings (which are the .NET reference type System.String) will only be null if returned as such by a .NET API. String values returned by a .NET API should always be checked for nullness, since F# code generally assumes string values are non-null. null is not otherwise part of the F# type system. Thus the compiler does not check that you insert null checks after API calls. If you do not insert a null check then just as in C# you will get a null pointer exception whenever you try to call a method on the object.
Aside: It is likely that a future version of F# will include an extension in its type system to cover these circumstances, along with an analysis of the .NET libraries to determine nullness conditions. Some other Microsoft Research projects are proposing and annotating common .NET libraries with nullness information, and F# would piggy-back off that. Aside: It is possible to generate null values of any F# type through a number of backdoor routes, e.g., by taking the null object value and casting it to an F# type, or by using reflection to create a null value of an F# type, or by using other .NET languages, or by using "optimized" functions such as Array.zero_create. The runtime behaviour of such programs is unspecified and may raise a NullReferenceException. However, overall memory safety will not compromised as a result of this. |