I am currently writing a library that encodes and decodes images in qoi format. This image format uses rgb and rgba pixel formats.
I made the IPixel interface and two implementations RgbPixel and RgbaPixel. IPixel is not intended to be used as an extension point (because there are only rgb and rgba), I made this interface to use generic classes so as not to write the same thing twice.
RgbPixel and RgbaPixel are structs with the [StructLayout] attribute. Some of my generic classes use unsafe code to unsafely cast these structs and other unmanaged types to each other to improve performance, so if user/another implementation of IPixel gets to them, it will most likely cause an exception.
To avoid all these problems, I would like to allow only internal implementation of the IPixel interface. But I don't know how to do it in C#.
If IPixel were an abstract class, I could prevent implementation from other assemblies by having only internal constructors. But I can only use interfaces because I need RgbPixel and RgbaPixel to be structs.
.netstandart 2.0, c# 10.0
CodePudding user response:
I would make IPixel internal and have public shim methods that accept RgbPixel and RgbaPixel which then call the common method that accepts IPixel (or a base class).
CodePudding user response:
I made all the class constructors internal and added two static methods Load and New<T>, the first one takes an encoded data and by decoding the header it creates either QImage<RgbPixel> or QImage<RgbaPixel>, the second method merely checks the type parameter passed by the user, as suggested by Kirk Woll in the comments.
Looks something like this:
public class QImage : IImage {
internal QImage(/*args*/) { /*...*/ }
public static QImage Load(byte[] bytes) {
//...
var qr = new QoiBytesReader(bytes);
var qHeader = Decoder.DecodeHeader(ref qr);
//ugly :|
return qHeader.Channels == Channels.Rgb
? new QImage<RgbPixel>(Decoder.DecodePixels<RgbPixel>(ref qr, in qHeader), qHeader)
: new QImage<RgbaPixel>(Decoder.DecodePixels<RgbaPixel>(ref qr, in qHeader), qHeader);
}
//...
}
public class QImage<T> : QImage where T : unmanaged, IPixel {
internal QImage(/*args*/) : base(/*params*/) { /*...*/ }
public static QImage<T> New(byte[] pixels, QHeader qHeader) {
if (typeof(T) == typeof(RgbPixel) || typeof(T) == typeof(RgbaPixel))
return new QImage<T>(pixels, qHeader);
throw new NotSupportedException($"Specified type <{typeof(T).Name}> is not supported. Use <{nameof(RgbPixel)}> or <{nameof(RgbaPixel)}> instead.");
}
//...
}
internal static unsafe class Decoder {
internal static byte[] DecodePixels<T>(ref QoiBytesReader qr, in QHeader metadata)
where T : unmanaged, IPixel { /*decode*/ }
//...
}
