PART 1

image

This content assumes you have an introductory knowledge of C# 3.0 language features in .NET 3.5 and mastery of legacy C# 2.0 Generics, Generic Type Constraints, Anonymous Delegates and related material.

I use a ‘pair programming’ approach with continued refactoring as this is how I would discuss it if you were coding with me, with an unfortunate one-way delivery.

The Brief Strategic View

Microsoft has slowly been moving C# in a very productive direction (this is not new, as these features existed in 2.0 although not nearly as well integrated) to provide ‘Functional Language’ features. If you don’t know or care about language semantics, just know that Linq and especially Lambada Expressions are about empowering you to use executable code like a variable, aka to leverage the power of functional programming. For more on this, read this MSDN article by Joel Pobar (former CLR Team) or read the next set of posts (part 2 onward) as I will go into the depths of this.

I think of Lambadas as an incredibly focused and powerful domain specific language for delegates.

In this sense they are quite similar to Regular Expressions in that they are really good at their focus area.

What do I mean by good?

  • Terse yet Understandable / Maintainable
  • Syntax tailored to the need, not the other way around.
  • Highly effective for problems that are orders of magnitude more difficult without them (a simple 10% improvement would not cut it)

Painless Intro

I’ll start with a fairly trivial, yet important example (I use it every day). Many times when comparing Strings I want to ignore case and culture (the InvariantCulture). This is provided by an overload as such as you likely know:

[Test,Category("BaselineCore")]
public void shouldAsserStringCloneInvokeEqual() {

var baselineString = “This is a TEST CASE to IgnOrE Casing”;

var stringUpper = baselineString.ToUpperInvariant();

Assert.IsTrue(baselineString.Equals(stringUpper,
StringComparison.InvariantCultureIgnoreCase));

The test results are shown above. Pass. Ok so I really like short, concise code that is understandable at a glance. Also it’s a pain to always (even with ReSharper) use this (and I have seen people use RegEx for this! RegEx is awesome but overkill for this issue).

Refactoring

  • Create an extension method on String
  • Decide on a good name for the method (this is SO important and for most an afterthought!)

I’ve settled on calling this new method on String ‘EqualsCore’ as that is what we are doing, making the conditions for a match ‘simpler’ and seeing ‘just the core values’ are the same (anyway it makes sense to me)., I suppose this could be ‘EqualsRelaxed’ or whatever..

Here is the test case (no code yet):

[Test, Category("BaselineCore")]
public void shouldAsserStringsEqualUsingExtension() {
var baselineString = “This is a TEST CASE to IgnOrE Casing”;
Assert.IsTrue(baselineString. EqualsSimple(baselineString.ToUpperInvariant())); }

Now we write the code. Here is the container for the extension method:

public static class StringExtensions {

public static bool EqualsSimple(this string sTarget, string compare) {

return sTarget.Equals(compare, StringComparison.InvariantCultureIgnoreCase); }

}

Indeed they both pass:

image25

Since every type inherits from Object, and Equals is defined on Object, all instances should support this approach, and I could be early bound by using Generics…. Hmm…

I tried this (note: I gave it a new new ‘EqualsThis’ to separate them.

public static bool EqualsThis<TTarget>(this TTarget sTarget, TTarget compare){

return sTarget.Equals(compare);

}

Functionally not that interesting at all, but a test. So I typed in the following and wow… It works from Intellisense’s view… Ok it compiled! Wait…..FAIL! But why:?

OK here is the new test:

[Test, Category("BaselineCore")]

public void shouldAsserANYTHINGEqualUsingExtension() {

const String baselineString =”This is a TEST CASE to IgnOrE Casing”;

var sb = new StringBuilder(baselineString);

Assert.IsTrue(sb.EqualsThis(new StringBuilder(sb.ToString())));

}

Interesting…

Here is the documentation for what Equals means by default from Microsoft:

Returns: true if objA is the same instance as objB or if both are null references or if objA.Equals(objB) returns true; otherwise, false.

So our code fails using the extension yet this returns true:

[Test] public void shouldAssertStringBuilderExplicit() {

const String baselineString = “This is a TEST CASE to IgnOrE Casing”;

var sb = new StringBuilder(baselineString);

var sb2 = new StringBuilder(baselineString); Assert.IsTrue(sb.Equals(sb2));

}

So Reflector to the rescue once again. I could see in Reflector what I believed the issue was. Indeed the StringBuilder class has an overloaded Equals, and even making the extension method cast to the generic type directly was a no go.

So what do you think? Why would this compile fine with absolutely no problems (and that is correct it turns out), but FAIL at runtime on the assertion when the same line above passes? Skip ahead and reply with the answer if you know it….

This exposes one of the dangers that we must be incredibly careful with. It has always been poor design in my opinion to encourage developers to override common methods such as ToString() and Equals(object X) with their own behaviors as you force consumers of the API to understand IMPLEMENTATION. You cannot ensure your OK simply from a contract. This is known to be evil….

Of course this is a legacy style and will be slowly phased out.

Spin up reflector and look at the code for the OVERLOAD that StringBuilder has:

public bool Equals(StringBuilder sb){if (sb == null) return false;

return (((this.Capacity == sb.Capacity) && (this.MaxCapacity == sb.MaxCapacity))
&&
this.m_StringValue.Equals((string) sb.m_StringValue));

}

Of course! How else could a StringBuilder claim to be ‘Equal’ to another… In fact it is perfectly reasonable but again shows the danger of late binding, making assumptions about how any ‘object’ type will perform.

So there was no real way for our extension to call the ‘correct’ equals. It called the base definition given above which is obvious now why it failed.

So how do we fix this for the general case?

Here is the test case which I got working.. If your not familiar with this style of code,

This is the foundation we build layer after layer on and illustrates the core of this post.

[Test] public void shouldAsserANYTHINGEqualUsingExtension() {

const String baselineString = “This is a TEST CASE to IgnOrE Casing”;

var sb = new StringBuilder(baselineString);

var sb2 = new StringBuilder(baselineString);

Assert.IsTrue(sb.EqualsThis(x => x.Equals(sb2)));

}

Lambadas are like an incredibly focused and powerful domain specific language for delegates. In this sense they are quite similar to Regular Expressions in that they are really good (and to quantify good, I mean clear yet precise, not overly verbose yet highly effective for problems that are more difficult without them).

So what about the implementation? Here it is:

public static bool EqualsThis<TTarget>(this TTarget sTarget,

Predicate<TTarget> EqualsDelegate) {

return EqualsDelegate.Invoke(sTarget);

}

It’s all about Expressions! Think of them as varied ways to receive executable code that you can ‘invoke’ literally, that must meet the contract you define.
This is so basic after we cover what the really useful applications are. However remember this has nothing to do with the examples, only the concepts they represent.

  1. Combine Generics and Generic Constraints to your Extension Methods but BE CAREFUL and ensure you are covered by unit tests
  2. Try to always think a level of abstraction above where your immediate need is to see if your solution indeed has wider and perhaps far more valuable contribution.
  3. Hide complexity behind your Framework API, and focus on crafting work that others will easily consume.


2 Comments

  1. Your explanation is quite confusing. For anyone else reading this, there are two slightly odd things going on:

    1. StringBuilder provides an Equals(StringBuilder sb) method, but doesn’t override Object.Equals. This is very unusual. (IMO, the StringBuilder.Equals method is useless anyway; who cares if two StringBuilders have the same capacity?)

    2. The EqualsThis generic method always calls Object.Equals, whatever type it is instantiated with. That’s how generics work.

  2. Thanks for the feedback… I first say ‘it’s not about the specific example, it’s about the concept’. Here the concept is where you can go wrong (one of many examples, this was the one at the time that seemed easy to explain and illustrate).

    The item to be aware of here is that it looks perfectly OK, compiles then fails at run-time. So again, it’s not about how interesting the ‘StringBuilder’s Equals situation is or is not’. It’s about the larger concept.

    A common problem in writing this kind of material is readers want to make it about the specific examples. It’s 100% about the concepts.

    Thanks for your feedback. If your interested I’d love to get feedback on ‘one level of abstraction’ above where you read this from in your previous statement.

    Thanks,
    Damon Wilder Carr


Post a Comment