F# Generic Inlining With Member Constraints

by Marc Sigrist 13. July 2012 16:05

In this post, we are going to create a Converter class who converts values to strings. The conversion rules are specified by passing the name of a culture to the class’ constructor. The culture name can contain just the language (as ISO 639-1 alpha-2 code, e. g. “en”) or the language and region (as ISO 3166 code, e. g. “US”) combined with hyphen, e. g. “en-US”. An empty culture name “” specifies the invariant culture, which falls back to “en”.

The class has only one conversion method ToString, who is generic. It takes a single parameter, which is the value to be converted. The parameter’s type is inferred to have a member constraint, who restricts the type (at compile time) to have an instance member with the signature
ToString: IFormatProvider -> string. The parameter’s member is invoked with a member constraint invocation expression.

open System
open System.Globalization
 
type Converter(cultureName:string) =
    /// The culture used by this converter.
    member val Culture = CultureInfo.GetCultureInfo cultureName

    /// Converts value to a string, based on the specified culture.
    member inline self.ToString value =
        (^T: (member ToString: IFormatProvider -> string) 
        value, self.Culture)
 
// Test
let germanConverter = Converter "de"
let nrString = germanConverter.ToString 1234.643  // "1234,643"
let dtString = germanConverter.ToString <| 
               DateTime(2003, 11, 25, 17, 38, 47) // "25.11.2003 17:38:47"

Edit (Oct 15, 2012)

 The converter's culture is now exposed via a public property, based on F# 3.0 auto property syntax. (The Culture's value is evaluated only once, during class construction time.) Previously, the culture was a private field, and the example could not compile in a regular source file, due to access rule violation. Strangely, it did compile in a scripting file; this is an example where the scripting compiler (wrongly) does not behave in the exact same way as the regular compiler.

This is a blog post in the “Brief tip” category. It is meant as a starting point for further investigation, not as a complete copy/paste solution to a specific concern you may have.

Yet another comment on the C# cast ( ) and „as“ operators…

by Marc Sigrist 21. February 2010 19:39

Time and again, the question comes up whether to prefer the cast ( ) or the „as“ operator in C# to perform conversions to reference types. According to the C# 3.0 language specification, the two operators behave differently in several respects:

C# Conversion Operator( )as
Name Cast operator as operator
C# 3.0 Specification section 7.6.6 7.9.11
Can convert to ValueType Yes No
Executes user-defined explicit conversions Yes No
Compile-time errors CS0030: Cannot convert type 'X' to 'Y'. CS0030: Cannot convert type 'X' to 'Y'.
CS0077: The as operator must be used with a reference type or nullable type ('Y' is a non-nullable value type).
Runtime behavior if not convertible Throws InvalidCastException: "Unable to cast object of type 'X' to type 'Y'." Returns null

Given the above comparison table, it follows that you must use the cast operator ( ) in the following cases:

  • to convert to a ValueType:
    object o = 137; var i = (int)o;
  • to convert via a user-defined explicit operator overload defined in the source type. Note that the overload may return anything whatsoever: The same instance, a new instance of the same type, a new instance of a completely non-related type, null, a value type or reference type...
    // Declared inside a class called Source:
    static explicit operator Target(Source s) {return new Target();}

By contrast, you must use the "as" operator in the following cases:

  • to be 100% sure that that the result points to the same instance, or null, but never anything else:
    var bar = foo as Bar;
  • to return null if a runtime conversion to the desired reference type is not possible, as in this construct:
    var bar = foo as Bar ?? new Bar();

Furthermore, the "as" operator is a bit more tolerant with generics at compile time than the cast operator. That’s because the as operator casts „directly“ to a type of the same inheritance line:

TDerived GetDerived<TBase, TDerived>(TBase b) where TDerived: class {
    // Produces compiler error CS0030: Cannot convert
    // type 'TBase' to 'TDerived'.

    // return (TDerived)b;

    // This compiles...
    return
b as TDerived;
}

In all other cases, from a technical point of view, you may freely choose between the cast and the "as" operator. Therefore, the „right“ decision depends on what you want to express most in the source code. If you use the "as" operator, you thereby clearly document that you are interested in a reference target type that points to the same instance, and nothing else whatsoever – i.e., you do not want some crazy custom explicit operator to produce any side effects! Moreover, as a matter of taste, the "as" operator makes your code less cryptic, because it uses no parentheses, whereas the cast ( ) operator looks like old-fashioned C code.

For these reasons, personally, I prefer the "as" operator over the cast ( ) operator. However, I have met other developers who almost religiously defend usage of the cast ( ) operator as a best practice, „because it immediately throws an InvalidCastException“ at runtime if necessary, while the "as" operator might only later produce a NullReferenceException. Yet, against this, it could be argued that the "as" operator is even more secure, because it does not allow you to compile if it’s a value type, and it will never produce side effects...

Some tools, such as JetBrains‘ ReSharper, warn you if you apply the "as" operator without checking for null with a null comparison within the same procedure, even if null is logically impossible; e.g., when you have defined a null-checking procedure in a separate external helper method. You can turn this off, but only on a per-user basis. Others who look at your code may still see the warning. You may also disable the warning by surrounding the „critical“ section with a specific ReSharper comment, but it’s annoying to have your code cluttered like this. Therefore, if your company uses ReSharper, this might be a pragmatic reason to prefer the cast ( ) operator over the "as" operator.

In my opinion, it would be better if the C# "as" operator did not return null, but throw an InvalidCastException if the direct cast is not possible. Nothing of importance would be lost by this – you can always check for null with the "is" operator - but a lot of misunderstandings and disagreements would be prevented. Interestingly, the VB.NET language has the DirectCast operator, which does exactly this. A similar behavior could be implemented in C# like this:

public static T As<T>(this object o) where T: class{
    if
(o == null) throw new NullReferenceException();
    if
(!(o is T)) throw new InvalidCastException();
    return
o as T;
}