Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

Lightweight Semantic Types in .NET

4.98/5 (28 votes)
13 Oct 2015CPOL5 min read 28.4K   317  
An efficient solution for adding semantic meaning, strong-type checking and validation to your data members.

Update October 10: v 1.1: Added performance benchmark and support for other types

Introduction

Instead of writing:

C#
int distance = 10;
int period = 1;
double temperature = 37.1;
String email = "john.smith@company.com";

Wouldn’t it be better if we could write:

C#
Integer<meter> distance = 10;
Integer<Milliseconds> period = 1;
Double<Celsius> temperature = 37.1;
String<Email> = "john.smith@company.com";

In this way, we can specify the meaning ("semantics") and the unit of measurements. The compiler can also prevent us to write stupid code like this:

C#
distance = period

This is exactly what this article presents a solution for that is

  • Easy to use​
  • Memory efficient (using value-typed structs)
  • Versatile, since it can be used just as ordinary int and double fields in WPF binding and serialization
  • Extendable, since customized validation also can be added

Background

Several solutions for semantic types have been presented here at Code Project this year. In January, Matt Perdeck presented the idea and motivation for semantic types in the excellent article, Introducing Semantic Types in .NET. More recently, Marc Clifton also presented his solutions for this in the article Strong Type-checking with Semantic Types. In this article, I will shortly present an alternative solution making use of value-types structs to save memory and time. This solution also supports custom validation rules and importantly, in-built support for value-conversion to support direct binding in WPF.

For introduction to why semantic types is a good idea, please read the referenced articles above.

Using the Code

To create a new semantic type, you first must declare it by creating a class for it:

C#
class Celsius : SemanticType { }

Then you can start to use in one of the generic semantic value-types, just like any other type, with the difference that you have in-build documentation of the unit of measurement:

C#
class Example { 
    public Double<Celsius> CurrentTemperature { get; set; } 
    public void IncreaseTemperatureBy(Double<Celsius> increment) { 
        CurrentTemperature += increment; 
    }

   public Integer<Celsius>? TargetTemperature { get; set; }
}

Validation

If you want, you can customize your semantic type even more with validation. If you want to ensure that the values are within range, you can override the IsValid function:

C#
public class Celsius : SemanticType
{
    public const double MinValue = -273.15;

    public override bool IsValid(ref double doubleValue) {
        return doubleValue >= MinValue;
    }

    public override bool IsValid(ref int value) {
        return value >= MinValue;
    }

    public override string GetInvalidMessage(object invalidValue) {
        return "Temperature in Celsius must always be higher than or equal to -273.15 degrees";
    }

    public static String HelpText {
        get { return "Temperature in degress Kelvin."; }
    }
}

If you want to use the same semantic type for different base types, e.g., doubles and integers, you must override the corresponding IsValid function. The value is sent is a ref-parameter to the IsValid function to allow this function to adjust the value.

As you see, you can also provide a customized error message. This is used as the message returned in exceptions and can be accessed in code. Other members can be added to be accessed from the Semantic property of the Double<T> or Integer<T> which returns an instance of the SemanticType.

Data-Binding in WPF

To support direct use as binding sources in WPF binding sources, the semantic types have custom type converters. This means that you can use properties of Double<TSemantic> just like double in bindings. Additionally, you can bind to the other properties of the SemanticType through the Semantic property. Perhaps, you want to provide Maximum and Minimum values to be used in a slider control? Or a default value?

Performance

Obviously, semantic types cannot be as performant as ordinary built-in primary types, but remember that support for validation is included and you are fault-proofing you code. I included a simple benchmark test creating and manipulating array items. On my computer, a typical result was the following in Release mode without debugging:

Result for 1000000 iterations:
Double:   4 ms
Semantic: 15 ms
ByRef:    166 ms

As you see, the raw manipulating of semantic type (Double<T>) takes about 4-5 times more time than an ordinary double in this particular case where you do not much else. This must, of course, be considered if you have heavy number-crunching applications. However, in ordinary application where you do a lot other stuff, this should be insignificant. Also remember that the memory consumption of the data does not increase a single bit by using the semantic alternative.

For comparison, I also included a test with a simple "semantic" ByRef type, i.e., a class that contains a value. As you see, initiating and manipulating data in such way is much slower and of course consumes a lot more memory space.

Support for Other Data Types

I provided support for Integer, Double and String here which should be sufficient for many ordinary applications. To support other data types, such as long, there is also a generic Data<TData,TSemantic> type included. To use this, you must simply implement a TSemantic class that implements the ISemanticType<TData> interface. See examples in the provided code.

Points of Interest

Low Memory Consumption Using Value-Types Structs

All semantic types are value-type structs with a single instance variable storing the value. This means that they do not consume more bytes in memory than the encapsulated base type. Compared to solutions which use class-types, this reduces memory consumption and pressure on the garbage collector. The semantic value-types are immutable in the same sense as ordinary primitive types (passed by value).

Custom Type Converters for Generic Types

To support direct data binding, I had to provide custom TypeConverter for the semantic types. For generic types, this was a bit challenging, but after reading a valuable StackOverflow post answer, it was easy to create a generic solution that can be reused for any scenario where you have a generic type that needs to have corresponding generic type converter (with the same type parameters). To use it, you just have to decorate the type with attributes, first the ordinary TypeConverter attribute specifying the GenericTypeConverter type as the converter type and then an additional attribute to specify the actual generic type converter type to use. When WPF needs a type converter for the type, a new instance of the GenericTypeConverter will be created. In its constructor, a new instance of the generic typed converter is created. All requests are then forwarded to the generic type converter.

Conclusions

Now we have another tiny solution for semantic typing in .NET. What do you think? Is it useful? Maybe the .NET platform could include support generic versions of all primitive types similar to this? This could possibly be implemented even more efficiently by throwing away the semantic checking during Just in time compilation.

History

  • October 8th, 2015 - First version published
  • October 10th, 2015 - Added performance benchmark and support for other types. (v.1.1)
  • October 13th, 2015 - Minor corrections to article text (no change in code)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)