How can you have a template function in C# which match only a special subset of good types with no common interface?

85 Views Asked by At

I would like to be able to write some C# code the non-repetetive way, using generics, like this:

   var t = someObject.CallMethod<T>();

or

   someObject.CallMethod(t);

where caller supplies the T/t they want to provide or consume.

And then, because I think static correctness checking of programs is awesome, I would like this to be a compiler error for some types but not others, based on how I built someObject. Generic type constraints look like they could help with this in their general shape... but unfortunately the pattern matching seems actually extremely limited in what it can do.

"Constraints can specify interfaces, base classes, or require a generic type to be a reference, value, or unmanaged type."

In this case the only base class I know T is going to have, is object, and I don't want to restrict whether is a reference, value, or unmanaged type. This leaves interfaces and.... yup, I don't think I really want to sprinkle interfaces, or provide interface wrappers, for every single type I use this method with (there's going to be a lot). Nor do I see a way to gradually add interfaces to an object?

Are there any tricks or workarounds which can be used to build an object of possibly 'dynamic' type (we need not write a class for it, the compiler will generate one), where you do have something like generic methods but that only work for a specific list of types that you've enabled when you constructed the object?

2

There are 2 best solutions below

2
Martin On

For now there is no pattern matching implemented in where T : ... clauses. The only way I think to accomplish this is to do sth. like:

public static void Caller()
{
    CallMethod("This is a string");
    CallMethod(1234);
}

public static void CallMethod<T>(T input)
{
    switch (input)
    {
         case string s:
              Console.WriteLine(s); 
              break;
         case int number:
              Console.WriteLine($"I am a int: {number}");
              break;
         default:
              throw new NotSupportedException("This type is not yet supported.");
    }
}

The downside is to have the Exception on runtime not as a compile(r) time error. If you really want to have it at compile time, I think for now you do not come around and add more code to your class.

public static void Caller()
{
    CallMethod("This is a string");
    CallMethod(1234l); // compiler error will appear here because of long
    CallMethod(1234); 
}

public static void CallMethod(string s) => CallMethodImpl(s);
public static void CallMethod(int n) => CallMethodImpl(n);

private static void CallMethodImpl<T>(T input)
{
    switch (input)
    {
         case string s:
              Console.WriteLine(s); 
              break;
         case int number:
              Console.WriteLine($"I am an int: {number}");
              break;
         default:
              throw new NotSupportedException("This type is not yet supported.");
    }
}
0
Tim Lovell-Smith On

I'm still noodling on this trying to find an answer to my own question. People's thoughts are so far very helpful for reinforcing my belief that generic types are unfortunately not going to be able to achieve my dream solution of strong-typing in the context where I need it.(At least, not in C# 10!) I do see one other remaining slightly disappointing possibility is just go the other low-tech way - enumerate goodness, instead of trying to use generic constraints (since thats impossible).

i.e.

class TheClass
{
    public void TheMethod(GoodType1 t) => PrivateMethod<GoodType1>(t);
    public void TheMethod(GoodType2 t) => PrivateMethod<GoodType2>(t);
    private void PrivateMethod<T>(T t) => //...the 'generic' solution that really only works for some types...
}

This may be verbose, but at least gives me the main property I am trying to achieve: TheClass.TheMethod(x) will compile only if and only if x is a supported type.