Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Closest C# equivalent to the F# match expression?

I'm in the situation where a lot of my classes are containers of well-known but unordered objects of different types, e.g. a container may look as follows:

public class Container
{
    public A A { get; private set; }
    public B B { get; private set; }
    public C C { get; private set; }

    public bool StoreIfKnown(object o)
    {
        // TODO...
    }
}

So if o is of type A it should be stored in the A property, type B in the B property and so on.

In F# the StoreIfKnown method could be written something like the following (excuse the syntax errors, my F# is not great and quite rusty):

match o with
| ?: A a -> A <- a; true
| ?: B b -> B <- b; true
| ?: C c -> C <- c; true
| _ -> false

But in C# the only way seems to be the rather verbose:

if (o is A)
{
    this.A = (A)o;
    return true;
}

if (o is B)
{
    this.B = (B)o;
    return true;
}

// etc.

return false;

I could do it with the as keyword to avoid the test/cast pattern which would be faster, but is even more verbose.

Is there any elegant way to do this in C#?

like image 233
Greg Beech Avatar asked Oct 02 '09 12:10

Greg Beech


4 Answers

You could author an extension method on 'o' and helper classes to enable a programming model like

o.Match<A>( a => { this.A = a; return true; } )
 .Match<B>( b => { this.B = b; return true; } )
 .Else( () => { return false; } )

But be wary of doing too much DSL-like hackery here, lest you end up with an API only you understand.

See also

http://blogs.msdn.com/lucabol/archive/2008/07/15/a-c-library-to-write-functional-code-part-v-the-match-operator.aspx

like image 82
Brian Avatar answered Sep 28 '22 20:09

Brian


Its not as nifty as Brian's solution, but this doesn't require defining a new DSL. You'll notice your repeating the following code:

if (o is {DataType})
{
    {Property} = ({DataType})o;
    return true;
}

Its easy enough to pull that template into its own method, resulting in something like this:

public class Container
{
    public A A { get; private set; }
    public B B { get; private set; }
    public C C { get; private set; }

    private bool TestProp<T>(object o, Action<T> f)
    {
        if (o is T)
            return false;

        f((T)o);
        return true;
    }

    public bool StoreIfKnown(object o)
    {
        return
            TestProp<A>(o, x => A = x) ||
            TestProp<B>(o, x => B = x) ||
            TestProp<C>(o, x => C = x) ||
            false;
    }
}

If you're working with reference types, you can take advantage of type inference with the following adjustments:

    private bool TestProp<T>(T o, Action<T> f)
    {
        if (o == null)
            return false;

        f(o);
        return true;
    }

    public bool StoreIfKnown(object o)
    {
        return
            TestProp(o as A, x => A = x) ||
            TestProp(o as B, x => B = x) ||
            TestProp(o as C, x => C = x) ||
            false;
    }
like image 42
Juliet Avatar answered Sep 28 '22 18:09

Juliet


I've been playing around with a little match builder (inspired by Brian's answer) which allows type checking, guard clauses, and returning of a result from the whole thing. It uses type inference so the only place you need to specify a type is where you actually want to.

So, imagining type C has an IsActive property which we want to be true, it would look something like this:

var stored = Match.Against(o)
    .When<A>().Then(a => { this.A = a; return true; })
    .When<B>().Then(b => { this.B = b; return true; })
    .When<C>(c => c.IsActive).Then(c => { this.C = c; return true; })
    .Otherwise(a => false);

Which I think is pretty readable, especially as it allows a predicate to be run against the derived type before actually matching which is something I do need.

The code is quite lengthy as it needs a number of partially-specified builder classes in the background to allow the type inference to work, so I can't really post it here. But if anyone's interested let me know in the comments and I'll stick it up on my blog and put a link here.

like image 20
Greg Beech Avatar answered Sep 28 '22 20:09

Greg Beech


Bart de Smet once went crazy with pattern matching, starting here (goes all the way up to part 8). If you ever manage to get through all this content there shouldn't be any questions left to pattern matching in C#. If there are, they probably cannot be answered by stackoverflow :)

like image 45
flq Avatar answered Sep 28 '22 20:09

flq