Click here to Skip to main content
13,150,178 members (60,106 online)
Click here to Skip to main content
Add your own
alternative version

Stats

19.1K views
14 bookmarked
Posted 18 Sep 2015

Strong Type Checking with Semantic Types

, 18 Sep 2015
Rate this:
Please Sign up or sign in to vote.
Use Semantic Native Types for Even Stronger Typing

Introduction

One of the drawbacks of common typed languages is loss of semantic information.  For example:

string zipcode = "12565";
string state = "NY";
WhatAmI(zipcode);
WhatAmI(state);

void WhatAmI(string str)
{
  Console.WriteLine("I have no idea what " + str + " is.");
}

This illustrates that the semantic meaning for zipcode and state is not known to the program -- it is merely a convenient label for the programmer to hopefully populate with the correct value.

The goal of this article is to create a means for implementing immutable semantic types that wrap (usually) native types, providing stronger type checking of parameters, and to do so in a way that is easy to define the semantic type and easy to use the semantic type.

Source Code

Implementing semantic types is trivial -- just copy and paste the interfaces and base classes described in the section "Behind the Scenes" and start implementing your own concrete semantic types.

"Stronger Typing" with Semantic Types

The implementation described below let's us write this instead:

Zipcode z = Zipcode.SetValue("12565");
State s = State.SetValue("NY");
WhatAmI(z);
WhatAmI(s);

// Now we can pass a sematic type rather than "string"

static void WhatAmI(Zipcode z)
{
  Console.WriteLine("Zipcode = " + z.Value);
}

static void WhatAmI(State z)
{
  Console.WriteLine("State = " + z.Value);
}

Now we have "stronger" type checking as a result of using semantic types in place of native types.  Another benefit of this approach is that the semantic type is immutable -- every call to SetValue instantiates a new semantic instance.  Using semantic types is therefore very advantageous in multi-threaded applications--in other words, semantic types implement a feature similar to functional programming.  Of course, this immutability can be easily defeated, but it's not recommend!

Behind The Scenes

The implementation behind the declaration of a semantic type involves a couple interfaces and an abstract base class:

/// <summary>
/// Topmost abstraction.
/// </summary>
public interface ISemanticType
{
}

public interface ISemanticType<T>
{
  T Value { get; }
}

/// <summary>
/// Enforces a semantic type of type T with a setter.
/// </summary>
/// <typeparam name="T">The native type.</typeparam>
public abstract class SemanticType<T> : ISemanticType
{
  public T virtual Value { get; protected set; }
}

/// <summary>
/// Abstract native semantic type. Implements the native type T and the setter/getter.
/// This abstraction implements an immutable native type due to the fact that the setter
/// always returns a new concrete instance.
/// </summary>
/// <typeparam name="R">The concrete instance.</typeparam>
/// <typeparam name="T">The native type backing the concrete instance.</typeparam>
public abstract class NativeSemanticType<R, T> : SemanticType<T>
  where R : ISemanticType<T>, new()
{
  public T Value { get { return val; } }

  protected T val;

  public static R SetValue(T val)
  {
    R ret = new R();
    ret.Value = val;

    return ret;
  }
}

The interface ISemanticType is merely one of convenience when the type information is not available.

The interface ISemanticType<T> is another convenience -- this allows us to pass instances of a semantic type without knowing, well, the semantic type.  In other words, it allows us to break the whole point of this article by passing a non-semantic interface instance, but sometimes that's necessary.

The abstract class SemanticType<T> implements an immutable Value property.  We need a protected setter so that the concrete semantic type can be instantiated with a static factory method, but we don't want the programmer to change the value once it's been set.

The abstract class NativeSemanticType<R, T> is where the magic happens.

  1. This class derives from SemanticType<T>, allowing it access to the base class' protected Value setter.
  2. The class takes R, the generic parameter of the concrete semantic type that is istelf derived from NativeSemanticType<R, T>. That's really the fun part - a class that takes generic type that is itself derived from that class.

Regarding that last point, the compiler is very picky about what type R can be.  In order for:

ret.Value = val;

to work, ret (being of type R) must be able to access the protected Value setter.  For this to work, R must be of type NativeSemanticType<R, T> -- it cannot be (though it would seem reasonable that it ought to be) of type SemanticType<T>.

Implementing Concrete Semantic Types

We can implement the concrete semantic types very easily.  In the example used earlier, the implementation is:

public class Zipcode : NativeSemanticType<Zipcode, string> { }
public class State : NativeSemanticType<State, string> { }

The only nuance to this is that the base class must specify the concrete class as the generic parameter R so that the base class' SetValue function knows what type to instantiate.  Because this is a static "factory" method, we really can't avoid this small awkwardness (at least I haven't figured out how to avoid it.)

The second generic parameter is the backing native type.  Of course, this doesn't actually have to be a native type -- it could be any other class as well. 

Additional Benefits of Semantic Types

Here's a couple reasons to consider semantic types:

Validation

Another neat thing about concrete semantic types is that the type implementation can override the value setter and perform checks.  For example:

public class Zipcode : NativeSemanticType<Zipcode, string> 
{
  public override string Value
  {
    get
    {
      return base.Value;
    }
    protected set
    {
      if (value.Length != 5)
      {
        throw new ApplicationException("Zipcode must have length of 5.");
      }

      base.Value = value;
    }
  }
}

This:

Zipcode fail = Zipcode.SetValue("123");

will now throw an exception.

Security

By using the concept illustrated above, you can ensure that the underlying value is secured, whether encrypted, hashed, or if a credit card, the credit card digits are masked, etc.

Semantic Computing / Distributed Semantic Computing

And of course, if you want to go whole hog, semantic types are also very amenable to multithreaded and distributed computing, as I've written about here.

Conclusion

While it may seem strange to code semantically, you may find this a useful technique to provide even stronger type checking of parameters, especially when dealing with function calls or class properties that have many of the same native types in common.  The ability to also provide value checking and other behaviors during the setting of a semantic value is an added bonus. 

License

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

Share

About the Author

Marc Clifton
United States United States
Marc is the creator of two open source projects, MyXaml, a declarative (XML) instantiation engine and the Advanced Unit Testing framework, and Interacx, a commercial n-tier RAD application suite.  Visit his website, www.marcclifton.com, where you will find many of his articles and his blog.

Marc lives in Philmont, NY.

You may also be interested in...

Pro

Comments and Discussions

 
PraiseThank you Pin
ronlease8-Jun-17 1:47
professionalronlease8-Jun-17 1:47 
QuestionMy vote of #5 Pin
BillWoodruff15-Feb-16 8:54
professionalBillWoodruff15-Feb-16 8:54 
AnswerRe: My vote of #5 Pin
Marc Clifton15-Feb-16 15:55
protectorMarc Clifton15-Feb-16 15:55 
QuestionVery cool, but I get compiler errors Pin
Val Cohen30-Sep-15 8:52
memberVal Cohen30-Sep-15 8:52 
AnswerRe: Very cool, but I get compiler errors Pin
Marc Clifton30-Sep-15 14:53
protectorMarc Clifton30-Sep-15 14:53 
GeneralRe: Very cool, but I get compiler errors Pin
Val Cohen30-Sep-15 14:56
memberVal Cohen30-Sep-15 14:56 
QuestionA good technique Pin
Stuart Dootson22-Sep-15 5:40
professionalStuart Dootson22-Sep-15 5:40 
AnswerRe: A good technique Pin
Marc Clifton22-Sep-15 9:27
protectorMarc Clifton22-Sep-15 9:27 
GeneralRe: A good technique Pin
Stuart Dootson22-Sep-15 11:55
professionalStuart Dootson22-Sep-15 11:55 
Generalterminology Pin
mrivier21-Sep-15 19:55
membermrivier21-Sep-15 19:55 
GeneralRe: terminology Pin
Marc Clifton22-Sep-15 3:02
protectorMarc Clifton22-Sep-15 3:02 
QuestionHmm Pin
FatCatProgrammer21-Sep-15 3:49
memberFatCatProgrammer21-Sep-15 3:49 
AnswerRe: Hmm Pin
Marc Clifton22-Sep-15 3:31
protectorMarc Clifton22-Sep-15 3:31 
GeneralRe: Hmm Pin
FatCatProgrammer28-Sep-15 9:04
memberFatCatProgrammer28-Sep-15 9:04 
QuestionReaders can also refer to this article Pin
wmjordan18-Sep-15 13:44
memberwmjordan18-Sep-15 13:44 
AnswerRe: Readers can also reference this article Pin
Marc Clifton19-Sep-15 2:35
protectorMarc Clifton19-Sep-15 2:35 
QuestionRe: Readers can also refer to this article Pin
ahagel21-Sep-15 11:28
memberahagel21-Sep-15 11:28 
AnswerRe: Readers can also refer to this article Pin
Marc Clifton22-Sep-15 3:36
protectorMarc Clifton22-Sep-15 3:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.170924.2 | Last Updated 18 Sep 2015
Article Copyright 2015 by Marc Clifton
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid