Discussing the nuts and bolts of software development

Thursday, July 10, 2008

 

Compile Time Semantic Checking

C# is a TypeSafe language, what about SemanticSafe?

In a project I'm working on we have a lot of database records ids being passed around as integers. Today I needed to add a new record id to this list. Adding the new item to the function signature is easy. The hard part comes in making sure the right value is being assigned.
Public void MyFunction(int TableA_ID, int TableB_ID, String someOtherValue);

int A_ID = 5;
int B_ID = 4;
MyFunction(B_ID, A_ID, "Hello World");

So what happens when I compile? Nothing, it works fine. Yet that line where I called the function has just corrupted the entire database because those database ID's were transposed. Which kinda sucks.

It's one thing to guarantee type safety, what what about semantic safety? An idea popped into my head to use the existing type-checking to accomplish this.
    public struct UserID
{
private int value;
public static implicit operator int(UserID source)
{ return source.value; }

public UserID(int intValue)
{
value = intValue;
}
}

private void TryToFail(UserID test) { }

UserId works = new UserId(5);
int fails = works;

TryToFail(works) //compiles fine
TryToFail(fails) //Doesn't compile

Even though both works and fails contain the exact same value, the function can be made to fail if the values are put in the wrong order. I used an implicit operator so that I didn't lose the convince of having a simple integer, neato.

Obviously this technique is not something you would want to use for every possible field as there is one extra step involved in getting the value. But if you only did this for the record IDs I think it would be enough gain to make it worthwhile.

I would like to think of a way to do this so that it has compile time checking, but once compiled only contains the primitive value. Any ideas?

Labels: ,


Comments:
This was one of the safety features of super-safe Ada. In Ada you can simply declare a new primitive type:
type UserID is new Integer;

Better yet you can do ranges to prevent overflows and negative values:
type UserID is range 0 .. 1000;

This does not work like typedef in C, in Ada new types are distinct and not interchangable, ie the compiler will complain.

To get this behaviour in C, you should not use typedef, but instead create a struct:
typedef struct {
int id;
} UserID;

The disadvantage is that you have to type user_id.id to get the actual value, but there is no runtime overhead for passing by value because UserID is the same size as int.

I am not sure if the C# compiler/runtime has any overhead for structs but I am willing to bet GCC (and any decent compiler) will compile both int and UserID to the same code.
 
Neato. Yeah, range checking would be nice. I know I've seen it done with attributes but it used a special compiler and cost $$ to use I think.

I like that C# allows me to be lazy by using implicit operators ;) you can just use it as if it were a regular integer without any problem (but you can only update the reference with another UserId so it still gaurentees the value makes sense)
 
Post a Comment



<< Home

This page is powered by Blogger. Isn't yours?