Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
97 views
in Technique[技术] by (71.8m points)

c# - Is there any benefit to this switch / pattern matching idea?

I've been looking at F# recently, and while I'm not likely to leap the fence any time soon, it definitely highlights some areas where C# (or library support) could make life easier.

In particular, I'm thinking about the pattern matching capability of F#, which allows a very rich syntax - much more expressive than the current switch/conditional C# equivalents. I won't try to give a direct example (my F# isn't up to it), but in short it allows:

  • match by type (with full-coverage checking for discriminated unions) [note this also infers the type for the bound variable, giving member access etc]
  • match by predicate
  • combinations of the above (and possibly some other scenarios I'm not aware of)

While it would be lovely for C# to eventually borrow [ahem] some of this richness, in the interim I've been looking at what can be done at runtime - for example, it is fairly easy to knock together some objects to allow:

var getRentPrice = new Switch<Vehicle, int>()
        .Case<Motorcycle>(bike => 100 + bike.Cylinders * 10) // "bike" here is typed as Motorcycle
        .Case<Bicycle>(30) // returns a constant
        .Case<Car>(car => car.EngineType == EngineType.Diesel, car => 220 + car.Doors * 20)
        .Case<Car>(car => car.EngineType == EngineType.Gasoline, car => 200 + car.Doors * 20)
        .ElseThrow(); // or could use a Default(...) terminator

where getRentPrice is a Func<Vehicle,int>.

[note - maybe Switch/Case here is the wrong terms... but it shows the idea]

To me, this is a lot clearer than the equivalent using repeated if/else, or a composite ternary conditional (which gets very messy for non-trivial expressions - brackets galore). It also avoids a lot of casting, and allows for simple extension (either directly or via extension methods) to more-specific matches, for example an InRange(...) match comparable to the VB Select...Case "x To y" usage.

I'm just trying to gauge if people think there is much benefit from constructs like the above (in the absence of language support)?

Note additionally that I've been playing with 3 variants of the above:

  • a Func<TSource,TValue> version for evaluation - comparable to composite ternary conditional statements
  • an Action<TSource> version - comparable to if/else if/else if/else if/else
  • an Expression<Func<TSource,TValue>> version - as the first, but usable by arbitrary LINQ providers

Additionally, using the Expression-based version enables Expression-tree re-writing, essentially inlining all the branches into a single composite conditional Expression, rather than using repeated invocation. I haven't checked recently, but in some early Entity Framework builds I seem to recall this being necessary, as it didn't like InvocationExpression very much. It also allows more efficient usage with LINQ-to-Objects, since it avoids repeated delegate invocations - tests show a match like the above (using the Expression form) performing at the same speed [marginally quicker, in fact] compared to the equivalent C# composite conditional statement. For completeness, the Func<...> based-version took 4 times as long as the C# conditional statement, but is still very quick and is unlikely to be a major bottleneck in most use-cases.

I welcome any thoughts / input / critique / etc on the above (or on the possibilities of richer C# language support... here's hoping ;-p).

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

After trying to do such "functional" things in C# (and even attempting a book on it), I've come to the conclusion that no, with a few exceptions, such things don't help too much.

The main reason is that languages such as F# get a lot of their power from truly supporting these features. Not "you can do it", but "it's simple, it's clear, it's expected".

For instance, in pattern matching, you get the compiler telling you if there's an incomplete match or when another match will never be hit. This is less useful with open ended types, but when matching a discriminated union or tuples, it's very nifty. In F#, you expect people to pattern match, and it instantly makes sense.

The "problem" is that once you start using some functional concepts, it's natural to want to continue. However, leveraging tuples, functions, partial method application and currying, pattern matching, nested functions, generics, monad support, etc. in C# gets very ugly, very quickly. It's fun, and some very smart people have done some very cool things in C#, but actually using it feels heavy.

What I have ended up using often (across-projects) in C#:

  • Sequence functions, via extension methods for IEnumerable. Things like ForEach or Process ("Apply"? -- do an action on a sequence item as it's enumerated) fit in because C# syntax supports it well.
  • Abstracting common statement patterns. Complicated try/catch/finally blocks or other involved (often heavily generic) code blocks. Extending LINQ-to-SQL fits in here too.
  • Tuples, to some extent.

** But do note: The lack of automatic generalization and type inference really hinder the use of even these features. **

All this said, as someone else mentioned, on a small team, for a specific purpose, yes, perhaps they can help if you're stuck with C#. But in my experience, they usually felt like more hassle than they were worth - YMMV.

Some other links:


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...