Closest C# equivalent to the F# match expression?
Asked Answered
C

5

7

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

Coleville answered 2/10, 2009 at 12:13 Comment(5)
I'm just shocked there is actually someone out there using F#...Parnell
@pixelbobby: notice there are 478 F# questions, compared to 409 Haskell, 382 Scala, 353 Lisp, 305 Erlang, and 96 OCaml. F# looks to be the most popular functional programming language on SO, probably due to crossover from the hubFS community and .NET developers who want to jump on the functional programming train.Thessalonians
I wouldn't say I use F#, I just like to keep abreast of its features. Though I'd be keen to have a job where it was the main language I used (assuming suitability for purpose) to get my skills up! Knowing at least the basics of a number of languages which use paradigms different to those of the languages you normally work in improves your all-round programming ability.Coleville
@pixelbobby: F# is already used in production environments by Microsoft for XBox analytics and many financial groups!Bulbil
@pixelbobby: I write almost all new code in F#. I am now faster in F# after a few months. It is just a more productive language for data crunching. Depending on the code you write your mileage may vary. Even my C# code looks scarily functional. And I can confirm as stringer pointed out that many financial groups use it in production.Proleg
G
11

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

Guntar answered 2/10, 2009 at 12:17 Comment(2)
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.Moye
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.Coleville
T
9

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;
    }
Thessalonians answered 2/10, 2009 at 14:38 Comment(3)
+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.Coleville
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#.Belize
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.Quarterage
C
8

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.

Coleville answered 2/10, 2009 at 15:40 Comment(0)
S
2

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

Sidoney answered 2/10, 2009 at 15:52 Comment(1)
Looks interesting, I'll have a read through. Though unfortunately it seems his site has just broken temporarily :-SColeville
O
1

As of August 2016 and preview of C# 7.0, there is a limited support for pattern matching. You can try if by using Visual Studio “15” Preview 4.

According to the MSDN blog, you can use patterns in two places:

  • on the right-hand side of is expressions

  • in the case clauses in switch statements

Possible patterns are:

  • Constant patterns of the form c (where c is a constant expression in C#), which test that the input is equal to c

  • Type patterns of the form T x (where T is a type and x is an identifier), which test that the input has type T, and if so, extracts the value of the input into a fresh variable x of type T

  • Var patterns of the form var x (where x is an identifier), which always match, and simply put the value of the input into a fresh variable x with the same type as the input

I didn't install Visual Studio 15, so I'm not sure I rewrote your code correctly, but it should not be far off:

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 obj)
    {
        switch (obj)
        {
            case A a:
                this.A = a
                // I don't put "break" because I'm returning value from a method
                return true;
            case B b:
                this.B = b
                return true;
            case C c:
                this.C = c
                return true;
            default:
                WriteLine("<other>");
                return false;
        }
    }
}
Overplus answered 16/9, 2016 at 15:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.