Evolving My C# Style

I’ve done a lot of C# development over the past three years. I’m very happy with how the language has evolved.

During that time, my coding style in C# has also evolved. Here are some simple techniques I’ve found that have increased the quality of my C# code.

1. Use More Types

Wrapper Types

I’ve pretty much stopped using primitives as arguments, preferring instead to wrap them in a more meaningful type. This is particularly true for booleans. If a variable has two states, why not use an enum? For example, instead of this:


public void Move(float distance, bool goForward)

I use an argument of type Direction.


enum Direction
{
    Forward,
    Backward
}
//...
public Position Move(float distance, Direction goForward)

Your intent and comparison code will be less ambiguous. Plus, refactoring will be much easier later on when you add a new Direction value of Sideways. I’ll even wrap a String or an int if it helps make the intent clearer, and especially if I think there is a high likelihood that the format of the data might change.

So, for example, I might wrap the distance float in a type Distance like so:


class Distance {
    public float Value;
}

Now, if I need to chance distance to double for greater accuracy, it will be a one-line change instead of leaking throughout my code base.

Group-Related Arguments

Let’s continue with our Move() example. Say that we are passing both the distance and the direction to both the physics system and the animation system. Now, let’s say we want to add another parameter to Move(), specifying whether or not the player is crouching–something of interest to both the animation and physics subsystems. We’ll use an enum Stance instead of a bool, but we still have to add this parameter in at least three places:

  • The top-level Move function we’ve already seen
  • The physics system’s Move function
  • The animation system’s Move function

It pains me to have to add the same parameter with the same meaning in multiple places. So instead, let’s create a Movement type as follows:


public class Movement
{
    public Distance MoveDistance;
    public Direction MoveDirection;
    public Stance MoveStance;
}

This data structure can now be used as an argument to all three Move functions. This makes for much easier refactoring, and it provides a lot more semantic meaning to the data than float, bool, bool.

This may seem like first-year Computer Science stuff, but it is surprisingly easy to let extra arguments creep in without considering how they are related. Additionally, sometimes in C# it feels like you are adding tons of boilerplate to create simple types, especially if you want to make those types immutable. I’ve just come to the conclusion that it is nearly always worth doing so.

Delegate the Work

When I need to pass a function around, I nearly always prefer a specific, meaningful delegate type to Func<x,y,z>. To continue with our previous example, rather than something like this:

public void ProcessPhysics(Func<Position,Movement,Position> moveFunc); 

I prefer:

 public delegate Position MovementFunction(Position original, Movement delta); public void ProcessPhysics(MovementFunction moveFunc); 

When you just use Func, you’re setting yourself up for a world of refactoring pain. If you need to add or change the parameters to the Func<> you are passing around, you’ve got to find every place that signature is used, and then identify only the ones you wish to change. Additionally, all for delegates, you can give names to the arguments and the overall signature, thus allowing for much more precise semantic meaning in your code.

2. Rely on Immutable Data Structures

I’m a staunch proponent of immutable data structures. They are the natural corollary of writing code that is free of side effects. Unfortunately, C# doesn’t make using immutable data structures nearly as painless as it should be. C#6 lets you define get-only properties that you can set in the constructor, but if you want deep equality, you will have to write your own overload for Equals and GetHashCode (or let your tools write them for you). In any case, you end up with a lot of boilerplate, and you may introduce bugs in the process.

Nonetheless, the ability to have immutable data structures with deep equality is so valuable that I’ll deal with the pain. For one, deep equality makes it much easier to write test assertions. Declare the value you want to compare, and assert equality. No need to iterate over every field, and no chance of missing comparisons on new fields (provided you update Equals). I’d say deep equality is even more valuable for argument matchers in mocking tests. I hate writing


mockObj.Setup(obj => obj.Function("hello", 5, It.Is((SomeTypeOrOther arg) => MustBeAnExpressionThatReturnsTrue(arg))))

in Moq tests. Nine times out of ten, I write this code because I don’t have deep equality for SomeTypeOrOther. If I did, I would merely create a new object with the right composite values and do a simple argument matcher.


mockObj.Setup(obj => obj.Function("hello", 5, deeplyEqual))

From POCOs to POFRs

As a side note, one interesting cheat to get around the difficulty in defining immutable data structures in C# could be to define your data structures in F# as records. I’ve dubbed these data structures “Plain Old F# Records” or POFRs.

Defining POFRs in F# is really simple, even if you don’t know the language. When you refer to these types in C#, the interface to those types looks just like a normal object, but you get deep equality. Lists are a little different (you will see the FSharpList type instead of List), but converting to and from an FSharpList for any IEnumerable is pretty easy. Also note that FSharpList is not random access.

Nonetheless, if you’re feeling a bit gun-shy on actually using F# for most of your code, consider defining some of your data structures there. As a side benefit, it will help you organize your code such that your data structures are all cleanly defined in one easy-to-locate place.

3. Use Fluent Syntax FTW

Given that C# lacks built-in support for function composition or pipelining, I think the fluent syntax is a great way to chain operations together. This is especially true when you can create these chained operations without muddying up the initial type by using extension methods.

Extension methods let you add “pure” functions that look like member functions when used, and therefore can be used for fluent chaining. In fact, this is how LINQ is designed. Implementations of IEnumerable do not define Select or Where or Aggregate. Those are all defined as extension methods.

Putting together the idea of immutable data structures and fluent syntax, I like using fluent syntax for defining state transitions. So, for example, if the state was a wrapper around an integer,


initialState.Double().Add(5).DivideBy(3);

where DoubleAdd, and DivideBy could all be defined as extension methods.

Conclusion

If I were to pick a common theme for these changes to my C# coding style, it would be this: maximize explicitness while minimizing brittleness. Good code is very clear about its intent, and it does not resist refactoring.

How has your coding style evolved as you dive deeper into a language?

Conversation
  • Anders Baumann says:

    Hi Will.
    Nice article with some good advices.

    One point: Boolean and enum parameters always create duplication. This is because the called method contains a runtime test to decide between different code paths, despite the fact that the calling method already knows which branch it requires. Better to avoid the need for a boolean argument by creating several versions of the method, in which the meaning of the boolean is reflected in the names of the new methods.
    See http://silkandspinach.net/2004/07/15/avoid-boolean-parameters/

    Also, the type safe enum (aka. the strategy pattern) is a good alternative to enums (https://lostechies.com/jimmybogard/2008/08/12/enumeration-classes/). You can start out with an enum. If you see that you have to make several switch statements (shotgun surgery) then refactor to the type safe enum.

    /Anders

  • Comments are closed.