Click here to Skip to main content
14,875,632 members
Articles / Programming Languages / C#
Tip/Trick
Posted 15 Apr 2021

Stats

2.7K views
12 bookmarked

C# Code Reuse with a Traits Class

Rate me:
Please Sign up or sign in to vote.
5.00/5 (9 votes)
15 Apr 2021CPOL3 min read
A design pattern for C# that reuses a generic base class, modifying it using a traits class to allow changes that cannot be accomplished through overrides in an inherited class.
C# generic classes are quite useful for abstracting out type details until later use, but there is no mechanism in C# that allows non-type details to be abstracted in a similar manner. Non-type details can be overridden for instance members, but these won't be accessible to static members and during construction might not be reliable. This pattern facilitates providing the non-type details to the generic abstract base class at the time it is inherited into a concrete class, in some cases leaving nothing to be implemented in the derived class.

Introduction

When writing code to interop with native libraries, I found that it was helpful to have a class that would wrap strings for passing into native code, but all these classes were identical except the length of the byte array that would be passed. After finding a few changes and manually propagating them through copied-and-pasted code, I decided to find a way to remove the harmful redundancy in the source code. The answer I came up with was to use a special class to present the "traits" to the generic class in a similar way that a traits class provides details to C++ templates. This example here is simply for my use case, but the method could likely find broader usefulness, so I decided to share it in the hope it would.

Background

There is no way designed into the C# language to modify static behaviors of the base classes in the derived classes. Usually, the appropriate way to accommodate details that must change is to create a new instance of the same type, such as a new instance of a string wrapper that requires the byte array to be size 8, but in my code I wanted the size to be built into the type, since wherever that type is used, the size must correspond to the type.

Using the Code

The implementation that will be reused is done in an abstract generic class. The class accepts a single type parameter which must be derived from an interface that determines what non-type parameters will be provided to the abstract class. Of course, this type parameter must also provide a parameterless constructor so that it can be instantiated in the base class' static constructor, since there is no way to pass anything into the static constructor from the derived class--the whole point of this design pattern is to work around that limitation.

Since the code I wanted to reuse must utilize an abstracted integer value, the Traits interface provides a single property of type int:

C#
interface StringTraits
{
    int StringSize { get; }
}

The reusable implementation code then is placed in an abstract generic class which accepts a type parameter implementing the Traits interface which is specific to this implementation:

C#
abstract class StringBase<TTraits>
    where TTraits : StringTraits, new()
{
    static readonly TTraits _traits = new TTraits();
    static int StringSize => _traits.StringSize;

    public readonly byte[] Data;

    public StringBase()
    {
        Data = new byte[StringSize];
    }

    public void Assign(string value)
    {
        var utf8 = Encoding.UTF8.GetBytes(value);
        if (utf8.Length > StringSize)
        {
            throw new ArgumentException
            ($"The UTF-8 encoded value of the string must be {StringSize} bytes or less");
        }
        Array.Copy(utf8, Data, utf8.Length);
        Array.Clear(Data, utf8.Length, Data.Length - utf8.Length);
    }
}

Finally, for each concrete implementation that is required, I implement the traits interface, providing this implementation as a type parameter to the base class:

C#
class String8Traits : StringTraits
{
    int StringTraits.StringSize => 8;
}

class String8 : StringBase<String8Traits>
{
    // All code is in the base class
}

This last code block was repeated two more times for string sizes of 26 and 40. I later added implicit conversion to/from string for ease-of-use, but that's not important for this example.

Points of Interest

The native library I was using had three different string sizes I needed. Using this approach, I was easily able to have any changes propagate to all three different derived classes, ensuring that my strings went in and out of the third party library without any issues.

Another case where I used this pattern for was for configuration classes, when I wanted to change the name of the file that was used by static functions in derived classes. I do hope that this approach will find broader use than just my interop code, as copy-and-paste is always the worst way to reuse code.

History

  • 16th April, 2021: First revision

License

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

Share

About the Author

Thoits
United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 Pin
Сергій Ярошко18-Apr-21 20:17
professionalСергій Ярошко18-Apr-21 20:17 
QuestionInteresting idea Pin
Bernhard Hiller16-Apr-21 4:54
MemberBernhard Hiller16-Apr-21 4:54 

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.