tags:

views:

280

answers:

4

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#?

+8  A: 

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

Brian
very elegant approach...
Thomas Levesque
And put the extension method in its own namespace, or people will complain that you've added two methods to the intellisense list for all types.
Daniel Earwicker
Yeah I was wondering about something like this. As posted the type inference doesn't quite work properly (you'd have to write Match<A, bool> which is a little messy) but after a little playing around I think I can make something similar work and remove that issue. I'll post the results when I'm done.
Greg Beech
+7  A: 

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;
    }
Juliet
+1 - This is actually quite similar to what I've got at the moment (I didn't want to cloud the question with this much detail though). It works pretty well, but it just feels a little clunky.
Greg Beech
While Brians approach is more clever, this is the way I would implement it in C#. Very clean and straightforward. The F# approach looks great in F#, but not so much in C#.
Stuart Branham
Note that you can skip the final `false` values, since `a||false` is equivalent to `a`, and `a||b||false` is equivalent to `a||b`, etc.
kvb
+3  A: 

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.

Greg Beech
+1: Awesomeness :)
Juliet
+2  A: 

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 :)

flq
Looks interesting, I'll have a read through. Though unfortunately it seems his site has just broken temporarily :-S
Greg Beech