4

Question is simple: I'm using reflection to get a value. Then if it's a struct, I'm calling a method FooStruct, else FooClass:

Type type = x.GetType();
foreach (var fieldInfo in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
    var val = fieldInfo.GetValue(value);
    object obj = type.IsValueType ? val.FooStruct() : val.FooClass();
    fieldInfo.SetValue(x, obj);
}

problem is that FooStruct has a constraint:

public static T FooStruct<T>(this T value) where T : struct
{
    //...
}

so question is: is it possible to call a method with struct constraint for an object which contains a boxed struct instance without reflection?

stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
Alex Zhukovskiy
  • 9,565
  • 11
  • 75
  • 151
  • Seeing how you use GetType(), you're already using reflection :) – Carra Jan 15 '15 at 14:40
  • 1
    We don't have enough information, as you are only using reflection in your example. Please explain further what the problem is. – Yuval Itzchakov Jan 15 '15 at 14:41
  • 1
    Do you mean: is it possible given an object which contains a boxed struct instance? You want to call the method on the variable `val` right? – helb Jan 15 '15 at 14:58
  • 1
    Do you realise that, if it is a reference type, you're only ever calling `FooClass`, never anything more specific? – Rawling Jan 15 '15 at 15:00
  • Constraints are ignored in extension methods even without using reflection. – Brannon Jan 15 '15 at 15:08
  • @Brannon that's not true – Alex Zhukovskiy Jan 15 '15 at 15:12
  • @AlexJoukovsky, if you search the web for ambiguous type constraint on extension methods, you'll see many hits. Here is one example: http://stackoverflow.com/questions/7179460/why-generic-extension-method-with-constraint-is-not-recognized-as-extension-meth – Brannon Jan 15 '15 at 15:18
  • It *may* be possible to build Expression that does it - cast + call method for particular type... (not sure if it would work so). – Alexei Levenkov Jan 24 '15 at 07:00
  • @Brannon I would not call that "constraints are ignored in extensions methods" so... Constraint will be enforced when you call it directly. (also maybe some other comment got deleted and now your comments are somewhat unrelated) – Alexei Levenkov Jan 24 '15 at 07:08

4 Answers4

2

I'd happily be proven wrong by another answer, but I don't think this is possible without resorting even more to reflection. See further below for the reason that makes me suspect this. See end of the answer for a reflection-based solution.

Practical suggestion: I would simply drop the constraint on your FooStruct and FooClass methods, and additionally:

  • either make them non-generic and accept an argument of type object (which is what val is declared as, anyway). There's no advantage to having these methods be generic if they are only ever passed objects;

  • or cast val from object to T before invoking FooStruct / FooClass.

Why does it seem impossible to do what you're asking? You are trying to convert an expression that is statically typed object (namely val) into something that is statically typed <T> where T : struct or <T> where T : class (in order to call the respective extension method on such a T). That is, you are trying to dynamically introduce a new type variable inside your foreach loop. Unfortunately, the only way to introduce a type variable is to declare it in advance, i.e. as some generic type parameter T in the method's signature; and then it is not the code inside your method that gets to choose what actual type it stands for—it's the calling code that determines T.

Reflection-based solution:

// determine which method ought to be called based on `val`'s run-time type.
// (for C# 6 and later, use the `nameof` operator instead of hard-coding method names)
Type type = val.GetType();
string fooName = type.IsValueType ? "FooStruct" : "FooClass";

// bind to the generic method and supply the type argument for it:
// (I'm assuming that your extension methods are defined in `FooMethodsClass`.)
MethodInfo fooOpen = typeof(FooMethodsClass).GetMethod(fooName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
MethodInfo foo = fooOpen.MakeGenericMethod(new Type[] { type });

// invoke the generic (extension) method with `val` as the `this` argument:
foo.Invoke(null, new object[] { val });
stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
1

The dynamic variable support will set T appropriately. I use this trick regularly. Try it like this:

Type type = x.GetType();
foreach (var fieldInfo in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
    dynamic val = fieldInfo.GetValue(value);
    object obj = type.IsValueType ? Utilities.FooStruct(val) : Utilities.FooClass(val);
    fieldInfo.SetValue(x, obj);
}
Brannon
  • 5,324
  • 4
  • 35
  • 83
  • Interesting! Two questions: **1.** Does the declaration of `Utilities.FooStruct` look any different from the OP's version, or is it exactly the same? **2.** Given that we're dealing with `dynamic`, is there any advantage in keeping the generic type constraints on `FooStruct` and `FooClass`? – stakx - no longer contributing Jan 15 '15 at 15:17
  • It should be the same declaration. (I made a guess as to the utility class name as it wasn't specified.) – Brannon Jan 15 '15 at 15:19
  • I've just tried this and I get `The type 'dynamic' must be a non-nullable value type in order to use it as parameter 'T'` i.e. you can't pass a `dynamic` as a `struct`? (Although at run-time you can't pass a struct into the class-constrained method either, even if it's dynamic...) – Rawling Jan 15 '15 at 15:21
  • I think there is great advantage in keeping the method generic; it's common to use the `typeof(T)` or to trigger generic delegates. The constraint seems less valuable, especially as it's incorporated in the method name. There may be other reasons for that in the code base, though. – Brannon Jan 15 '15 at 15:21
  • 1
    @Rawling, maybe we do need to drop the struct constraint and rely upon the method name alone. – Brannon Jan 15 '15 at 15:25
  • @Brannon That'll work... I'm not sure what the `struct` constraint gains you once you're inside the method anyway :) – Rawling Jan 15 '15 at 15:26
  • Looks like it will not work like this unfortunately. It will give compilation error https://dotnetfiddle.net/VJ4jdy – Nick Jan 15 '15 at 15:27
  • 1
    @Imortist, the parameter type in your sample needs to be `T`, not `ValueType`. Also, it apparently doesn't compile with the constraints. the dotnetfiddle site gives a destabilization error, but that error doesn't show up on a local build. – Brannon Jan 15 '15 at 15:56
  • @Brannon Thanks Brannon. You are right! Did a lot of tests on the same example and missed parameter type issue – Nick Jan 15 '15 at 16:03
1

Apparently you can call the methods with reflection and they work without a problem:

using System;
using System.Reflection;

namespace DemoDynamicT
{
    public static class Utilities
    {
        public static T FooStruct<T>(this T value) where T:struct
        {
            return default(T);
        }

        public static T FooClass<T>(this T value) where T : class
        {
            return default(T);
        }
    }

    public class Program
    {
        class TestClass
        {
            public TestStruct StructField;
        }

        struct TestStruct
        {
            public int x;
            int y;
        }

        public static void Main()
        {
            var x = new TestClass();
            Type type = x.GetType();
            foreach (var fieldInfo in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
            {
                var val = fieldInfo.GetValue(x);
                var methodInfo = typeof(Utilities).GetMethod(fieldInfo.FieldType.IsValueType ? "FooStruct" : "FooClass");
                var toBeCalled = methodInfo.MakeGenericMethod(fieldInfo.FieldType);
                object obj = toBeCalled.Invoke(null, new [] {val});
                fieldInfo.SetValue(x, obj);
            }
        }
    }
}
Brannon
  • 5,324
  • 4
  • 35
  • 83
0

I don't think you can do this directly. You can try workaround like this:

public static class Utilities
{
    public static ValueType FooStruct(this ValueType value)
    {
        //put your code here
        return default(ValueType);
    }

    public static object FooClass(this object value)
    {
        //put your code here
        return null;
    }

    public static T FooStruct<T>(this T value) where T: struct
    {
        return (T) FooStruct(value);
    }

    public static T FooClass<T>(this T value) where T: class
    {
        return (T) FooClass(value);
    }
}

public class Program
{
    class TestClass
    {
        public TestStruct StructField;
    }

    struct TestStruct
    {
        int x;
        int y;
    }

    public static void Main()
    {
        var x = new TestClass();
        Type type = x.GetType();
        foreach (var fieldInfo in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
        {
            var val = fieldInfo.GetValue(x);
            object obj = fieldInfo.FieldType.IsValueType ? ((ValueType)val).FooStruct() : val.FooClass();
            fieldInfo.SetValue(x, obj);
        }

        //Generic call
        var structVar = new TestStruct();
        structVar.FooStruct();
    }
}
Nick
  • 798
  • 1
  • 7
  • 14