Welcome to the "C# light" project page.

As you have probably guessed, "C# light" is not a real project. It was inspired by this post by Phil Trelford.

However, the clean syntax and features of "C# light" are based closely on the F# language, which this site is devoted to.

Many people think that F# has a strange syntax, and are put off by the name. I thought that if I created a "C# light" slideshow that demonstrated the benefits of the F# syntax under another name, skeptical C# developers might be more open-minded!

So, if you are a skeptical C# developer, let me show you that F# is almost identical to "C# light", with only a few minor tweaks.

"C# light" compared with F#

Here is the "C# light" example that I used throughout the slides:

class Person(string name, DateTime birthday) =

    /// Full name
    member Name = name 

    /// Birthday
    member Birthday = birthday

And here is the equivalent F# code:

type Person(name :string, birthday :DateTime) =

    /// Full name
    member this.Name = name 

    /// Birthday
    member this.Birthday = birthday

There are a few minor syntax changes:

  • The keyword class is replaced with type
  • The type annotations are "backwards". Rather than string name, the parameter is declared as name:string
  • The this keyword is added to the member declarations.

But other than that, the code is very similar.

The DTO example

Here is the example in the C# light slide deck which uses a syntax similar to anonymous types to declare a simple DTO class:

class Person = {string name, DateTime birthday}

var person = {name="Alice", birthday=Today}

And here is the F# equivalent:

type Person = {name :string; birthday :DateTime}

let person = {name="Alice"; birthday=DateTime.Today}

There are two more changes here:

  • The var keyword is replaced by let in F#.
  • Commas are replaced by semicolons in both the definition and usage of the class.

Automatically generate code for equality

As promised by the slides, the F# compiler does automatically generate equality code for most types.

type Person = {name :string; birthday :DateTime}

let alice1 = {name="Alice"; birthday=DateTime.Today}
let alice2 = {name="Alice"; birthday=DateTime.Today}

if alice1 = alice2 then
    Console.WriteLine("Alice1 and Alice2 are equal")  // true!

Immutability

In F#, user-defined types are immutable by default. If you want to change them, you have to use the mutable keyword.

type Person = {name :string; birthday :DateTime}

//define an immutable Alice 
let alice3 = {name="Alice"; birthday=DateTime.Today}

//changing immutable Alice to Bob gives an error
alice3 <- {name="Bob"; birthday=DateTime.Today}  // This value is not mutable

//define a mutable Alice 
let mutable alice4 = {name="Alice"; birthday=DateTime.Today}

//changing mutable Alice to Bob is ok
alice4 <- {name="Bob"; birthday=DateTime.Today}
Console.WriteLine("Alice's name is " + alice4.name)

Non-null reference classes

In F#, user-defined types are not allowed to be null.

type Person = {name :string; birthday :DateTime}

let mutable alice4 = {name="Alice"; birthday=DateTime.Today}
alice4 <- null  // error
   // The type 'Person' does not have 'null' as a proper value

Allow anonymous types to implement interfaces

Here's how an object instance can implement an interface in F#, in this case IDisposable.

do 

    // create it with a "using" block
    use tempDisposable = 
      {new IDisposable with 
        member this.Dispose() = Console.WriteLine("Disposed") }

    // do something             
    Console.WriteLine("Doing something") 
    
    // tempDisposable goes out of scope and is disposed

The console output is:

Doing something 
Disposed

In F#, these are actually called "object expressions".

Allow subclasses to be merged into a single "case" class

In F#, these are called "discriminated unions" and they are one of the best things about F#.

Here's the C# light version:

class PaymentMethod = 
| Cash
| Check(int checkNo)
| Card(string cardType, string cardNo)

And here's the F# version:

type CheckNo = int
type CardType = Visa | Mastercard
type CardNo = string

type PaymentMethod = 
| Cash
| Check of CheckNo
| Card of CardType * CardNo

Here's the C# way of constructing an object:

PaymentMethod cash = Cash();
PaymentMethod check = Check(123);
PaymentMethod card = Card("Visa", "4012888888881881");

And here's the F# way:

let cash = Cash
let check = Check 123
let card = Card (Visa, "4012888888881881")

Finally, here's the C# way of deconstructing a payment:

void PrintPayment(payment) =   
   switch (payment) 
   {
     case Cash : // print cash
     case Check(checkNo) : // print check info
     case Card(cardType,cardNo) // print card info
   }

And here's the F# way:

let printPayment payment =   
   match payment with
   | Cash -> printfn "Cash"
   | Check checkNo -> printfn "Check %i" checkNo
   | Card (cardType,cardNo) -> printfn "Card %A %s" cardType cardNo
   
printPayment cash
printPayment check
printPayment card   

Interested in learning more?

The F# code for these examples is available on .Net Fiddle here. Do play with them to convince yourself that F# really does fulfill all the promises of C# Light!

If you want to see more about F#, this page is a good starting point for browsing this site. Or visit fsharp.org for more about F# in general.

And for more about the power of F# types, check out my slide show on "Domain Driven Design with F# types", below. And there is more DDD stuff here.

Comments

blog comments powered by Disqus