3

Let's say I have a method:

public void ExampleMethod<T>(T x) where T : struct // or new()
{
    // example usage, similar to what's actually happening
    var z = (T)Enum.Parse(typeof(T), privateFieldOfTypeString);

    // do something depending on values of x and z
    // possibly using other methods that require them being enums

    // or in case of the new() constraint:
    // var t = new T() and do something together with x
}

and I want to use it as follows:

public void CallerMethod<S>(S y)
{
    if (typeof(S).IsEnum) // or some other invariant meaning that `S` is "new()able"
    {
        ExampleMethod(y); // won't compile
    }
}

So, during run-time I know that S satisfies the constraint for ExampleMethod<T>. I know it's possible to call it using reflection, something along the lines of:

this
    .GetType()
    .GetMethod(nameof(ExampleMethod<object>))
    .MakeGenericMethod(typeof(S))
    .Invoke(this, new object[] { y });

Is it possible without reflection?

Note: this is a simplified code from a real-life example and obviously I have no control over the signatures of these methods, hence the answers "add the constraint to CallerMethod" and "remove the constraint from ExampleMethod" are invalid.

Yes, the whole thing should be redesigned so the whole problem wouldn't appear at all. But as often in real-life "the whole thing" is too big, too coupled and too risky to rewrite. Some requirements have changed in an unexpected way - hence the apparent code smell, which I'm trying to minimize by hiding it in a single nasty-looking place.

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
  • **You** know that `S` satisfies the constraint for `ExampleMethod`, but the compiler does not. You would need to add the constraint to `CallerMethod` if possible. That is the nature of generic members. – Nkosi Nov 23 '18 at 09:41
  • @Nkosi Going along your formulation: the question is "how to inform the compiler about this fact". An analogy from outside of generics is casting - you tell the compiler that you know that a particular variable is of a particular type. – BartoszKP Nov 23 '18 at 09:43
  • casting is runtime – jazb Nov 23 '18 at 09:44
  • @BartoszKP I am at a loss in the simplified example of how the two methods would come to interact with each other. Could some context be provided to get an idea of the intended goal so as to rule this out as an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Nkosi Nov 23 '18 at 09:50
  • @Nkosi I don't think so. The details are irrelevant, and this is the fact that I'm stuck with such two methods and I need to call one from another. They are on different types and different assemblies, but these details are really irrelevant. I am fully aware that the whole thing should be redesigned so this problem wouldn't appear at all. IMHO the question is clear - is it possible, and if yes - how? The possible expected answers are easy to imagine: "no, it's not possible, by the language rules/compiler restrictions" or "yes, it's possible, this is how". – BartoszKP Nov 23 '18 at 09:55
  • @BartoszKP noted, – Nkosi Nov 23 '18 at 09:55
  • ok another question after giving it some more thought. you check for type being enum. is it going to be a specific enum type or multiple enum types? – Nkosi Nov 23 '18 at 10:16
  • @Nkosi Many different enums possible. I can add some specifics in the method. – BartoszKP Nov 23 '18 at 10:17
  • I was thinking taking the long approach and casting to the known types if `S is KnownEnum` using `if (typeof(S).IsEnum) { var z = (KnownEnum)Enum.Parse(typeof(S), y.ToString()); ExampleMethod(z); }` But I guess you would have already tried that idea. – Nkosi Nov 23 '18 at 10:40
  • @JohnB Without casing `int x = someObject;` will not compile, with casting it will. This is the same situation with generic parameters. – BartoszKP Nov 23 '18 at 11:06
  • Can you use operator overloading: `CallerMethod(Enum1 enum) { }`, `CallerMethod(Enum2 enum) { }` etc? How many variations of `CallerMethod` can there be? – qujck Nov 23 '18 at 14:07
  • @qujck I have no control over `CallerMethod`'s signature as well. This is similar idea to one of the current answers. In general it would work, but is not maintainable for large number of different types. In my case it's also this way - not only many types, but new ones are added constantly. – BartoszKP Nov 23 '18 at 14:29
  • @BartoszKP could you use a t4 template to generate a `partial` class with the overloaded method signatures at compile time? – qujck Nov 23 '18 at 14:30
  • @qujck Our build system is a huge machinery as well, extending it would be tricky. But in general what you propose seems like a valid solution to the question as it is - you could provide an answer for others :) – BartoszKP Nov 23 '18 at 14:40

4 Answers4

3

You could use dynamic:

if (typeof(S).IsEnum)
{
    ExampleMethod((dynamic)y);
}
Dirk
  • 10,668
  • 2
  • 35
  • 49
2

You could utilise operator overloading; define multiple explicit versions of CallerMethod that can all successfully make a follow on call to ExampleMethod

CallerMethod(Enum1 value) { ExampleMethod(value); }
CallerMethod(Enum2 value) { ExampleMethod(value); }
CallerMethod(Enum3 value) { ExampleMethod(value); }

etc.

If there are a large number of growing types that need a version of CallerMethod you could write a T4 template to generate a partial class with all implementations.

qujck
  • 14,388
  • 4
  • 45
  • 74
1

If the enum types are known, one possibility, though verbose, would be to convert to known types.

For example, as a starting point,

public void CallerMethod<S>(S y) {
    if (typeof(S).IsEnum) {
        if (y is KnownEnum) {
            var z = (KnownEnum)Enum.Parse(typeof(S), y.ToString());
            ExampleMethod(z);
        }
        if (y is KnownEnum2) {
            var z = (KnownEnum2)Enum.Parse(typeof(S), y.ToString());
            ExampleMethod(z);
        }
        //...
    }
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
-3

In your specific case you can just cast to an int.

public void CallerMethod<S>(S y)
{
    if (typeof(S).IsEnum)
    {
        ExampleMethod((int)y);
    }
}
John Wu
  • 50,556
  • 8
  • 44
  • 80
  • 1
    That wont compile either `Cannot convert type 'S' to 'int'` – Nkosi Nov 23 '18 at 10:08
  • 1
    Not every enum use `int` as its underlying type. Also what if `ExampleMethod` do thing based on actual enum type (for example calling `ToString()` on argument)? With casting to `int` you will lose this type information. – user4003407 Nov 23 '18 at 10:11