C#: Creating a generic extension method with a type parameter for a generic class without specifying the class type parameter

Viewed 139

I would like to have a generic immutable data class with a list of extensions, which can be accessed using extension methods. I can do this with the following code:

public class GenericData<T> where T : unmanaged
{
    public GenericData(T data, IList<IDataExtension> extensions)
    {
        Data = data;
        Extensions = extensions.ToImmutableList();
    }

    public T Data { get; }

    public IImmutableList<IDataExtension> Extensions { get; }
}

public interface IDataExtension { }

public static class GenericDataExtensions
{
    public static TExtension GetExtension<TExtension, TDataType>(this GenericData<TDataType> data)
        where TExtension : IDataExtension
        where TDataType : unmanaged
    {
        return data.Extensions.OfType<TExtension>().First();
    }
}

This works fine, but requires me to pass in TDataType every time I want to get an extension, even though the data type isn't needed for the extension method implementation.

public interface IEvenlySpacedXAxisExtension : IDataExtension
{
    EvenlySpacedXAxisInfo Info { get; }
}

// later...

var xAxisInfo = data.GetExtension<IEvenlySpacedXAxisExtension, float>().Info;

I could solve this by putting GetExtension() behind an interface and making GenericData implement it:

public class GenericData<T> : IHaveDataExtensions where T : unmanaged
{
    public GenericData(T data, IList<IDataExtension> extensions)
    {
        Data = data;
        Extensions = extensions.ToImmutableList();
    }

    public T Data { get; }

    public IImmutableList<IDataExtension> Extensions { get; }
}

public interface IDataExtension { }

public interface IHaveDataExtensions
{
    IImmutableList<IDataExtension> Extensions { get; }
}

public static class IHaveExtensionsExtensions
{
    public static TExtension GetExtension<TExtension>(this IHaveDataExtensions objectWithExtensions)
        where TExtension : IDataExtension
    {
        return objectWithExtensions.Extensions.OfType<TExtension>().First();
    }
}

This works, but requires an unnecessary layer of abstraction and looks pretty messy. Is there a cleaner way to achieve the same result?

1 Answers

My use case was similar and what worked for me is writing an extension for type T. The extension class also has a static property to hold the "immutable list of extensions".

public static class GenericDataExtensions
{
    public static TExtension GetExtension<T>(this T any)
    {
        Console.WriteLine($"Requesting extension for type '{any.GetType().Name}'");
        if(ImmutableListOfExtensions.TryGetValue(typeof(T), out TExtension tExtension))
        {
            return tExtension;
        }
        return default(TExtension); // To do: Handle case of 'not found'.
    }
    internal static IReadOnlyDictionary<Type, TExtension> 
}

The mock of the TExtension class goes like so:

public class TExtension
{
    public TExtension(string name) => Name = name;
    public string Name { get; }
}

And here is a simple test driver that initializes the dictionary and retrieves an extension:

[TestMethod]
public void InferredExtensionGetterTest()
{
    // Initialize the static extension class
    var initializer = new Dictionary<Type, TExtension>
    {
        {typeof(A), new TExtension("Extension for class A") },
        {typeof(B), new TExtension("Extension for class B") },
        {typeof(C), new TExtension("Extension for class C") },
    };
    GenericDataExtensions.ImmutableListOfExtensions = initializer;

    var classA = new A();
    var classB = new B();
    var classC = new C();

    TExtension extension;
    extension = classA.GetExtension();
    Console.WriteLine($"Retrieved: {extension.Name}{Environment.NewLine}");

    extension = classB.GetExtension();
    Console.WriteLine($"Retrieved: {extension.Name}{Environment.NewLine}");

    extension = classC.GetExtension();
    Console.WriteLine($"Retrieved: {extension.Name}{Environment.NewLine}");
}

enter image description here

Related