Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

C# Generics – Constrain generic method parameter to have specific static method

I am working with auto-generated code classes, where every class Foo has an equivalent FooSerializer class with a static Serialize(Foo data) method.

I wanted to write a helper function that can execute the Serialize method (+ a bunch of other stuff) for any such class, by recieving two type params.

public void Serialize<Foo, FooSerializer>(Foo data)
{
  FooSerializer.Serialize(data);
}

But this cannot work, since it doesn’t know that FooSerializer has the Serialize method, and FooSerializer does not derive from any generic interface that defines that method as based on Foo.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

Is there a way to specify in the generic constraints that FooSerializer must have a Serialize method that recieves a param of type Foo?

In c++ I implemented this by simply making Foo and FooSerializer as template arguments. Wonder if there is a similar solution in c#.

>Solution :

On modern runtimes (not .NET Framework etc), yes:

interface IFooSerializer<T>
{
    // pick your own API here; string, byte[], Stream, etc
    static abstract T Read(string value);
    static abstract string Write(T value);
}

class Consumer
{
    public void Serialize<Foo, FooSerializer>(Foo data)
        where FooSerializer : IFooSerializer<Foo>
    {
        var s = FooSerializer.Write(data);
        var clone = FooSerializer.Read(s);
    }
}

However, it may be easier to just use an instance serializer.

In the case of generated code, you might need to put the interface part in a partial class, i.e.

partial class MyType : IMyStaticInterfaceThing {}

which simply adds IMyStaticInterfaceThing to the MyType definition, but will happily resolve the static methods from the generated code.


Runnable version:

using System;
using System.Text.Json;

var obj = new Consumer().Serialize<MyType, MyJsonSerializer<MyType>>(
    new() { Name = "Fred" });
Console.WriteLine(obj.Name);

class MyType
{
    public string? Name { get; set; }
}

interface IFooSerializer<T>
{
    static abstract T Read(string value);
    static abstract string Write(T value);
}

class MyJsonSerializer<T> : IFooSerializer<T>
{
    public static T Read(string value)
        => JsonSerializer.Deserialize<T>(value)!;

    public static string Write(T value)
        => JsonSerializer.Serialize<T>(value);
}
class Consumer
{
    public Foo Serialize<Foo, FooSerializer>(Foo data)
        where FooSerializer : IFooSerializer<Foo>
    {
        var s = FooSerializer.Write(data);
        return FooSerializer.Read(s); // clone
    }
}

Or an alternative construction using the partial class aspect:

using System;
using System.Text.Json;

var obj = new Consumer().DeepClone<MyType>(
    new() { Name = "Fred" });
Console.WriteLine(obj.Name);

partial class MyType // the generated half
{
    public static MyType Read(string value)
        => JsonSerializer.Deserialize<MyType>(value)!;

    public static string Write(MyType value)
        => JsonSerializer.Serialize<MyType>(value);

    public string? Name { get; set; }
}

partial class MyType : ISerializable<MyType> { } // your half

interface ISerializable<T>
{
    // describe the API offered by the generated code
    static abstract T Read(string value);
    static abstract string Write(T value);
}

class Consumer
{
    public T DeepClone<T>(T data)
        where T: ISerializable<T>
    {
        var s = T.Write(data);
        return T.Read(s); // clone
    }
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading