C
C#2y ago
peppy

❔ Default implementation of a `static abstract` base interface member in derived interface

I am trying to implement an idea I saw in MessagePack for C#:
public interface IMemoryPackable<T>
{
static abstract void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref T? value)
where TBufferWriter : IBufferWriter<byte>;
static abstract void Deserialize(ref MemoryPackReader reader, scoped ref T? value);
}
public interface IMemoryPackable<T>
{
static abstract void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref T? value)
where TBufferWriter : IBufferWriter<byte>;
static abstract void Deserialize(ref MemoryPackReader reader, scoped ref T? value);
}
where using static abstract surfaces the methods on the type itself, which I find very useful. I came up with this:
public interface IDispatchable<T>
{
public static abstract bool Initialize();
public static abstract bool Serialize(in ROS<T> src, in Span<byte> dest, out int bytesWritten);
public static abstract bool Deserialize(in ROS<byte> src, in Span<T> dest, int srcLen);
}
public interface IDispatchable<T>
{
public static abstract bool Initialize();
public static abstract bool Serialize(in ROS<T> src, in Span<byte> dest, out int bytesWritten);
public static abstract bool Deserialize(in ROS<byte> src, in Span<T> dest, int srcLen);
}
Now- if T were a struct, I can provide good default impls, so I declare a derived interface that constrains T:
public interface IBlockDispatchable<T> : IDispatchable<T> where T : struct { }
public interface IBlockDispatchable<T> : IDispatchable<T> where T : struct { }
Can this interface now declare a default implementation of the static abstract members of IDispatchable<T> such that implementing structs surface that default implementation? I know the derived interface can declare an explicit impl of the base interface method, but then the explicit impl is not available through the struct's type as I expect static abstract/virtual members to be. If this is not the correct way to approach this, I'm open to all suggestions on how I can provide specialized behavior to structs in this particular instance. Thank you!
3 Replies
peppy
peppyOP2y ago
Could an idea here be to declare extension methods for IDispatchable<T> with struct constraints and give the implementations there instead of a derived interface? To further elaborate, the usecase is:
public static class Dispatcher<T> where T : IDispatchable<T>
{
public static bool WriteDataToDisk(in List<T> data)
{
// ...
T.Serialize(CollectionsMarshal.AsSpan(data), dest, out int bytesWritten);
}
// ...
}
public static class Dispatcher<T> where T : IDispatchable<T>
{
public static bool WriteDataToDisk(in List<T> data)
{
// ...
T.Serialize(CollectionsMarshal.AsSpan(data), dest, out int bytesWritten);
}
// ...
}
and I have tested this and it works perfectly fine when some struct/class implements IDispatchable<T> fully itself. The key point is that sometimes- for instance, structs where you don't want any custom behavior like ignoring fields; a correct (and more optimal) default impl can be provided with ease, and I see no reason to require types like that to write it out by hand. But if I do:
public interface IBlockDispatchable<T> : IDispatchable<T> where T : struct
{
static bool IDispatchable<T>.Serialize(in ROS<T> src, in Span<byte> dest, out int bytesWritten)
{
// Some default impl valid for all structs...
}
}
public interface IBlockDispatchable<T> : IDispatchable<T> where T : struct
{
static bool IDispatchable<T>.Serialize(in ROS<T> src, in Span<byte> dest, out int bytesWritten)
{
// Some default impl valid for all structs...
}
}
or however you're supposed to syntactically achieve this, my expectation is that for some struct:
public readonly struct SomeRoStruct : IBlockDispatchable<SomeRoStruct>
{
}
public readonly struct SomeRoStruct : IBlockDispatchable<SomeRoStruct>
{
}
SomeRoStruct.Serialize( ... ) should be available, use that default impl, and not require the struct to write it out. Mind you that it would be entirely valid for me to eliminate IBlockDispatchable<T> and simply write out the same exact implementation of IDispatchable<T>.Serialize in every struct, but surely there is a way to avoid having to write it out a hundred times over? The above example of BlockDispatchable<T> is trying to implement or at least override a static abstract base interface method, but I also have a usecase where the idea is to reabstract said base interface. Let's say that the user should provide custom (de)serialization calls at the class/struct level. I do not need them to implement IDispatchable<T>.Serialize as is, with a signature used for batches of data; it's enough to know how one item should be serialized and the method for a batch is trivially constructible. A reabstraction comes to mind like this:
public interface ICustomDispatchable<T> : IDispatchable<T> where T : ICustomDispatchable<T>
{
static bool IDispatchable<T>.Serialize(in ROS<T> src, in Span<byte> dest, out int bytesWritten)
{
bytesWritten = 0;

int sliceSize = T.CustomGetSize();
bool retval = true;

foreach (T item in src)
{
if (T.CustomSerializeOne(dest[bytesWritten..(bytesWritten + sliceSize)], item))
bytesWritten += sliceSize;
else
retval = false;
}

return retval;
}
// ...
public static abstract bool CustomInitialize();
public static abstract int CustomGetSize();
public static abstract bool CustomSerializeOne(in Span<byte> destSlice, in T t);
public static abstract bool CustomDeserializeOne(in ROS<byte> srcSlice, [NotNullWhen(true)] out T? t);
}
public interface ICustomDispatchable<T> : IDispatchable<T> where T : ICustomDispatchable<T>
{
static bool IDispatchable<T>.Serialize(in ROS<T> src, in Span<byte> dest, out int bytesWritten)
{
bytesWritten = 0;

int sliceSize = T.CustomGetSize();
bool retval = true;

foreach (T item in src)
{
if (T.CustomSerializeOne(dest[bytesWritten..(bytesWritten + sliceSize)], item))
bytesWritten += sliceSize;
else
retval = false;
}

return retval;
}
// ...
public static abstract bool CustomInitialize();
public static abstract int CustomGetSize();
public static abstract bool CustomSerializeOne(in Span<byte> destSlice, in T t);
public static abstract bool CustomDeserializeOne(in ROS<byte> srcSlice, [NotNullWhen(true)] out T? t);
}
but again, this way the default Serialize would not be available as SomeRoClass.Serialize( ... ) for some public class SomeRoClass : ICustomDispatchable<SomeRoClass> { ... } or the aforementioned SomeRoStruct if it chose to implement this for the sake of, say, ignoring a field or writing in extra data.
Accord
Accord2y ago
Looks like nothing has happened here. I will mark this as stale and this post will be archived until there is new activity.
peppy
peppyOP2y ago
In case anyone does somehow stumble upon this like a year later, the answer is https://github.com/dotnet/runtime/issues/79603. The code above is valid, but methods must be marked static virtual instead of static abstract with a default impl. Not great, but doable. The explicit interface implementation can be accessed with generic indirection.
Want results from more Discord servers?
Add your server