0

I'd like to stretch C#'s generics functionality to something like traits in C++, so that I can have a generic class that has behaviour defined in an interface specialising this generic. I know it is possible with static abstract methods when specialising with a class, but unfortunately not when specialising with an interface (because interface-defined methods don't take part in polymorphism).

Just to underscore - I need to specialise with interfaces, NOT with classes. Classes would work as written below in contradiction to interfaces. And I'd rather prefer to not introduce a dummy base abstract classes or some giant libraries implementing traits or cryptic nuget packages. It's just out of curiosity if I can do this more-or-less with C# 11 built-in features or not.

In other words, I'd like to have something like:

// a common library
public interface IBase {
    // actually I don't care which one of the below provides the data, 
    // can be method, property, whatever. I couldn't make any of them to work :(
    public static virtual string GetNamespace() => "IBase";
    public static string Namespace => "IBase";
    public static virtual string Bar() => "IBase";
}

// There are plenty of ISth interfaces defined in other libraries 
// I don't want to have a dependency on (so specialised extension methods 
// are rather unwelcome as well). Actually these libraries have no dependency
// on the common one too and would be cool to keep it that way
public interface ISth : IBase {
    // note: 'override' keyword is not accepted at all in interfaces
    
    // warning: hides inherited member, use 'new' keyword
    // note: invisible unless invoked explicitly ISth.GetNamespace()
    public static virtual string GetNamespace() => "ISth";
    
    // note: invisible unless invoked explicitly ISth.Namespace
    public new static string Namespace => "ISth";
    
    // warning: hides inherited member, use 'new' keyword
    // note: missing 'virtual' keyword, anyway invisible unless invoked explicitly ISth.Bar()
    public static string Bar() => "ISth";
}

// this is a common library with no dependency on ISth
public class Container<T> where T : IBase
{
    public static string TNamespaceFromGetter;
    public static string TNamespaceFromProperty;
    public static string TBar;

    static Container()
    {
        // takes GetNamespace() from IBase (due to T constraint), not from T
        // BUT if T were a class (not an interface), then it would take GetNamespace() from it
        TNamespaceFromGetter = T.GetNamespace();
        
        // error: Cannot do non-virtual member lookup in 'T' because it is a type parameter
        // TNamespaceFromProperty = T.Namespace;

        // takes Bar() from the most base type, not from T
        TBar = T.Bar();
    }
}

public class Program
{
    public static void Main()
    {
        var obj = new Container<ISth>();
        
        Console.WriteLine($"TNamespaceFromGetter property =
            {Container<ISth>.TNamespaceFromGetter}");

        // prints: TNamespaceFromGetter property = IBase
        // I'd like it to print: TNamespaceFromGetter property = ISth

        Console.WriteLine($"TBar property = {Container<ISth>.TBar}");
        // same as above
    }
}

If it were a C++, then plenty of options are available, e.g. a simple type trait defined along with ISth (it's a bit simplified code):

// in the common library header:
template<typename T>
struct Trait {};

// in the library with ISth header:
template<>
struct Trait<ISth> {
    static std::string Namespace = "ISth";
};

// again, in the common library, no dependency on ISth:
template<typename T>
struct Container {
    static std::string TNamespace = Trait<T>::Namespace;
    // or alternatively T could just follow some C++20's
    // concept requiring Namespace to already be in ISth:
    static std::string TNamespace = T::Namespace;
};

Is there a way to do this in C# in a kind of C++-style or is the generics implementation too simple?

I tried to override virtual static methods in ISth interface, but unfortunately interfaces themselves don't take part in polymorphism resolution. They can just introduce base implementation and only if deriving classes override it, then it is being taken. Any deriving interfaces can add new virtual (and also static) methods, but can't override the derived ones.

And my problem is that I want to attach some data (like namespace string) to an interface I'm specialising some factory class with, so that (long story short) factory would know in which namespace it is supposed to create objects. I just don't want to pass this metadata along as a constructor parameter, just make it automatic and implicit.

So the approach proposed e.g. here https://stackoverflow.com/a/53450332/2369637 sadly wouldn't work (it's about overriding method in the deriving class, not an interface).

Marcin Tarsier
  • 192
  • 2
  • 9

0 Answers0