AJ's blog

August 10, 2006

Generics. Boon and Bane…

Filed under: .NET, .NET Framework, C#, Software Development — ajdotnet @ 8:37 pm

As a developer I like certain things:

  1. I like type safety. Type safe access relieves me of “mind compiling” and puts the burden on the compiler (where it belongs). It also makes the code cleaner as it makes type casts unnecessary.
  2. I like shortcuts. Whenever I have to type the same 3 lines of code more than 2 times I’ll likely come up with a helper method. It’s easier to use, it documents the intention, it is less error prone, and it offers the opportunity to add additional checks. And after some time I will usually end up with several overloads.

Statement #1 led me to use generics. Rather than having code look like this:

Hashtable ht = new Hashtable();

ht["A"] = "first letter";

ht["Z"] = "last letter";string infoA = (string)ht["A"];

string infoB = (string)ht["B"]; // returns null

I can now write that:

Dictionary<string, string> dict = new Dictionary<string, string>();

dict["A"] = "first letter";

dict["Z"] = "last letter";string infoA = dict["A"];

string infoB = dict["B"]; // ???

Well. Then I discovered that the last line no longer returns null, rather it throws a KeyNotFoundException… 😦

Hey, Microsoft isnt’t shy of this little change in semantics. They even advertise it: “The following code example uses the Item property (the indexer in C#) to retrieve values, demonstrating that a KeyNotFoundException is thrown when a requested key is not present, and showing that the value associated with a key can be replaced. “ (http://msdn2.microsoft.com/en-us/library/9tee9ht2.aspx)

To overcome this little obstacle, I would have to write something like this:

string infoC = null;

try

{

    infoC = dict["C"];

}

catch

{

}

… or better this:

string infoD = null;

dict.TryGetValue("D", out infoD); // return value ignored

… which both violates my initial statement #2. And a helper method for just one generic incarnation simply wouldn’t do.

But we have generics. And why should the old C++ template tricks not work with generics as well? So I came up with a little generic helper class:

public class DictAdapter<TKey, TValue>

    where TValue: class

{

    public static TValue GetValue(Dictionary<TKey, TValue> dict, TKey key)

    {

        TValue value = null;

        if (!dict.TryGetValue(key, out value))

            return null;

        return value;

    }
Dictionary<TKey, TValue> _dict;
public DictAdapter(Dictionary<TKey, TValue> dict)

    {

        _dict = dict;

    }
public TValue GetValue(TKey key)

    {

        TValue value = null;

        if (!_dict.TryGetValue(key, out value))

            return null;

        return value;

    }
public TValue this[TKey key]

    {

        get { return Value(key); }

    }

}

Using this class I can get the dictionary value as before with on static method call, providing the dictionary and the key. If I have more than one of these calls I can create a temporay variable that holds the dictionary reference and simplify subsequent calls. With an object instance I can even leverage an indexer:

// static call

string infoE = DictAdapter<string, string>.Value(dict, "E");// temp. instance

DictAdapter<string, string> gv = new DictAdapter<string, string>(dict);

string infoF = gv.GetValue("F"); // returns null

string infoZ = gv.GetValue("Z");
// indexer call

string infoG = gv["G"];

Great, the lazy part of me is satisfied. But there is always the overly critical part in me, the part that cannot be convinced so easily…

Critical Point of View

Think of it: The strategy works just as it did in C++. And it is likely to produce the same problems.

Look at the last code snippet.

  • Do you understand immediately what every single line does?
  • If you know that the generic dictionary throws an exception if it does not contain the key, would you instantly guess that DictAdapter does not, thus effectively changing behaviour and semantics?
  • Would the call to TryGetValue no be more comprehensible, even at the cost of more and ugly code?

If you look closely at these questions and validate them against the effects intended with statement #2 (especially be more comprehensible and less error prone) this little class may as well be considered a failure.

Even if these questions would not arise, this little helper may be a problem in and of itself. It has happend with C++ code: When people started to realize the bennefit of these little helpers the result was often a bunch of helper classes, each one ususally written by one developer and never properly advertised, causing another developer to write similar helpers with slighly different behaviour, resulting in code fragments that looked the same but had subtle differences, especially under error conditions, and noone was ever able to maintain this mess properly, until eventually a huge code cleanup effort had to be undertaken, usually resulting in removing all of these classes and sometimes – but only rarely – in a clean and usefull set of new helpers, until someone realized the bennefit of these little helpers…. (take a breath, this was a very long sentence!).

Of course today we know better! Given our experience with C++ we will look for the helper classes that have broad usage scenarios (so we won’t see a flood of classes). We will use generics as type adapters to provide type safe access but otherwise mimic the behaviour of existing classes (so we will have obvious semantic and clearly defined responsibilities). We will refrain from unexpectedly hiding or changing semantics (so the code using the helpers will be self-documenting and in accordance with our expectations). And we will collect all those classes at the proper namespaces (so they will become public knowledge and we will instantly recognize overlapping behaviours with other classes in this namespace).
Additionally Microsoft has wisely decided to ommit some of the more suspect features of templates (e.g. constants as arguments or setting the base class as template argument), so we won’t become overly “inventive”.

But beware! It takes some disciplin to avoid the chaos. And since it requires the disciplin of every developer involved, my experience tells me that this is a battle that one is more likely to lose than win. (Hey, all it takes is a bright new right-out-of-the-university-and-I-know-everything jump start developer, the kind that did not yet validate theoretical academic knowledge against reality and common sense. Huh, he’s here – and he also knows of LINQ… :shock:.

That’s all for now folks,
AJ.NET

Advertisement

5 Comments »

  1. Hi Alex,

    yeah – Yesterday I stumbled about this new TryGetValue method too. But after thinking about it a moment, I think it’s a good enhancement, because it allows you to add NULL values to a hashtable.

    Perhaps it’s not obvious, but this feature missed in the good old dotNET 1.1 implementation. It was not well-defined if you got a NULL with the indexer because there was no key, or because the key had a NULL value.

    So everything has two sides.
    Cheers
    Gerhard

    Comment by Gerhard Stephan — August 11, 2006 @ 11:20 am

  2. Well, I guess the difference is:

    – with the HashTable I *may* call ContainsKey to distinguish between keys with null values and keys not present (if I didn’t care it didn’t get in my way, though)
    – with the Dictionary I *allways have to* use TryGetValue or check with ContainsKey to be on the safe side. And I better don’t forget that.

    There is no “information loss or gain”, it has just become a little harder if you are not interested in that particular bit of information.

    AJ.NET

    Comment by ajdotnet — August 11, 2006 @ 1:00 pm

  3. Hi Alex,
    only some coding stuff – at the begin of your examples you have followig codeline :

    string infoB = (string)ht[“A”]; // returns null

    you meant :
    string infoB = (string)ht[“B”]; // returns null
    ?

    Greetings
    Karl
    PS.: Your Blog developes very well!
    Karl

    Comment by Carl — August 14, 2006 @ 6:54 pm

  4. Thanks, I corrected it. (And I also noted that “Oracle Carl” found a coding bug that “C# Gerhard” and “.NET Alexander” missed 😉 – Congratulations!)

    Comment by ajdotnet — August 15, 2006 @ 7:10 am

  5. Yeah!
    I would like to have C# and a Mix of PL/SQL as the new PL/SQL.NET in Oracle 11;
    C# is fascinating to me and so i like to look into the code even i do not understand everything.

    karl

    Comment by Karl — August 15, 2006 @ 7:39 am


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: