Click here to Skip to main content
15,868,101 members
Articles / Programming Languages / C#

Strong Type Checking with Semantic Types

Rate me:
Please Sign up or sign in to vote.
4.93/5 (19 votes)
8 Dec 2018CPOL4 min read 40K   17   20
Use Semantic Native Types for even stronger typing

Introduction

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

C#
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 lets us write this instead:

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

// Now we can pass a semantic 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:

C#
/// <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:

C#
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:

C#
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 of 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:

C#
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:

C#
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)


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
QuestionThoughts on usage sub-contexts (of a common unit)? Pin
Chad3F12-Dec-18 10:37
Chad3F12-Dec-18 10:37 
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
mvaMarc Clifton15-Feb-16 15:55 
QuestionVery cool, but I get compiler errors Pin
Val Cohen30-Sep-15 8:52
Val Cohen30-Sep-15 8:52 
AnswerRe: Very cool, but I get compiler errors Pin
Marc Clifton30-Sep-15 14:53
mvaMarc Clifton30-Sep-15 14:53 
GeneralRe: Very cool, but I get compiler errors Pin
Val Cohen30-Sep-15 14:56
Val 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
mvaMarc 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
mrivier21-Sep-15 19:55 
GeneralRe: terminology Pin
Marc Clifton22-Sep-15 3:02
mvaMarc Clifton22-Sep-15 3:02 
QuestionHmm Pin
FatCatProgrammer21-Sep-15 3:49
FatCatProgrammer21-Sep-15 3:49 
AnswerRe: Hmm Pin
Marc Clifton22-Sep-15 3:31
mvaMarc Clifton22-Sep-15 3:31 
GeneralRe: Hmm Pin
FatCatProgrammer28-Sep-15 9:04
FatCatProgrammer28-Sep-15 9:04 
AnswerRe: Hmm Pin
Louis van Alphen10-Dec-18 9:19
Louis van Alphen10-Dec-18 9:19 
QuestionReaders can also refer to this article PinPopular
wmjordan18-Sep-15 13:44
professionalwmjordan18-Sep-15 13:44 
AnswerRe: Readers can also reference this article Pin
Marc Clifton19-Sep-15 2:35
mvaMarc Clifton19-Sep-15 2:35 
QuestionRe: Readers can also refer to this article Pin
ahagel21-Sep-15 11:28
professionalahagel21-Sep-15 11:28 
AnswerRe: Readers can also refer to this article Pin
Marc Clifton22-Sep-15 3:36
mvaMarc 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.