📜 ⬆️ ⬇️

Using CRTP Pattern in C #

CRTP ( Curiously recurring template pattern ) is an idiom leading its roots from C ++. The essence of CRTP lies in the inheritance from a generic class, the generic parameter of which is the heir class itself.

In code, it looks quite simple:
public class Base<T> where T : Base<T> { /* ... */ } public class Derived : Base<Derived> { /* ... */ } 

This approach allows you to operate on the type of the descendant class (T) in the code of the base class, for example, to explicitly cast this to the type T.

Consider a couple of practical applications.

')
The first of these is the implementation of the Fluent interface in terms of class inheritance:
 public class Rectangle<T> where T : Rectangle<T> { int _width; int _height; public T SetWidth(int width) { _width = width; return (T)this; } public T SetHeight(int height) { _height = height; return (T)this; } } public class Frame : Rectangle<Frame> { Color _color; public Frame SetColor(Color color) { _color = color; return this; } } 

An illustrative example of the result:
 var frame = new Frame() .SetWidth(100) .SetHeight(200) .SetColor(Color.White); 

The ability to call SetColor () is ensured by the fact that the SetWidth () / SetHeight () methods in this context return a Frame class object even when declared in the base Rectangle class.

The second option is to remove the generalized tasks in the static methods of the base class. At the same time, the logic that is necessary for the operation of these methods is implemented in the heir class.

Consider this on the example of the serialization of TItem elements by the TSerializer class:
 public abstract class SerializerBase<TSerializer, TItem> where TSerializer : SerializerBase<TSerializer, TItem>, new() { readonly static TSerializer _serializer; static SerializerBase() { _serializer = new TSerializer(); } public abstract void WriteAsBinary(TItem item, BinaryWriter writer); public static void Save(TItem item, BinaryWriter writer) { _serializer.WriteAsBinary(item, writer); } public static void Save(IList<TItem> items, BinaryWriter writer) { writer.Write(items.Count); foreach (var item in items) _serializer.WriteAsBinary(item, writer); } public static void Save(string name, TItem item, BinaryWriter writer) { writer.Write(name); _serializer.WriteAsBinary(item, writer); } } 

SerializerBase is an abstract class declared with two template parameters, and TSerializer must be a class with a parameterless constructor derived from SerializerBase itself. Inside there is a static field containing a singleton object of the heir class created in the static constructor. Overloaded Save methods call WriteAsBinary method on a singleton:
 public class GeoPoint { public double Lat { get; set; } public double Lon { get; set; } } public class GeoPointSerializer : SerializerBase<GeoPointSerializer, GeoPoint> { public override void WriteAsBinary(GeoPoint item, BinaryWriter writer) { writer.Write(item.Lat); writer.Write(item.Lon); } } 

Thus, by implementing the serialization code for one element, we are able to serialize both the list of elements and arbitrary data sets with the participation of TItem through the static methods GeoPointSerializer.Save, which are inherited from the base class.

Usage example:
 GeoPoint[] region = new GeoPoint[] { new GeoPoint { Lat = 0.0, Lon = 0.0 }, new GeoPoint { Lat = -25, Lon = 135 }, new GeoPoint { Lat = -20, Lon = 46} }; GeoPoint gp = new GeoPoint() { Lat = -3.065, Lon = 37.358 }; byte[] bytes; using (MemoryStream ms = new MemoryStream()) using (BinaryWriter writer = new BinaryWriter(ms)) { GeoPointSerializer.Save("Mount Kilimanjaro", gp, writer); GeoPointSerializer.Save(region, writer); bytes = ms.ToArray(); } 

In this case, CRTP helps to effectively separate the serialization logic from the data itself and provides convenient access to the methods. Such a solution can also be useful for implementing mappers of business logic classes in DTO and vice versa, if the use of automatic mappers is a bottleneck in performance.

Source: https://habr.com/ru/post/211743/


All Articles