2

I have the following interface

public interface ITypedValueProvider<TValue> 
{
    TValue GetValue(Type type);
}

I want to create extension method which would allow me to get type from generic type argument. I have written an extension method like following.

public static class Extensions
{
    public static TValue GetValue<TType, TValue>(this ITypedValueProvider<TValue> valueProvider)
    {
        return valueProvider.GetValue(typeof(TType));
    }
}

However, this extension method requires 2 type arguments, where I need to use only one, and hopefully other be inferred from the input generic class.

static class Program
{
    void Main()
    {
        ITypedValueProvider<int> valueProvider = new ValueProvider<int>(typeof(TargetValueConsumer));
        var value = valueProvider.GetValue<TargetValueConsumer, int>() //This line will work
        var value = valueProvider.GetValue<TargetValueConsumer>(); //This one, obviously, won't compile
    } 
}

public class ValueProvider<TValue> : ITypedValueProvider<TValue>
{
   private readonly (Type ConsumerType, TValue Value) _record;

   public ValueProvider(TValue value, Type type) 
   {
      _record = (type, value);
   }
 
   TValue GetValue(Type type) 
   {
       if (_record.ConsumerType == type)
           return _record.Value;
       return default;
   }
}

public class TargetValueConsumer {}

How can I write the extension method which won't require second type argument for the described interface or how to use existing method which will infer second type argument from the usage?

vkolesnik
  • 367
  • 2
  • 13
  • [Why doesn't C# infer my generic types?](https://stackoverflow.com/questions/8511066/why-doesnt-c-sharp-infer-my-generic-types) Also, mixing generic and `Type` instances doesn't look as good approach, IMO – Pavel Anikhouski Dec 11 '20 at 13:53
  • @PavelAnikhouski , thanks for your comment. Will take a look at the link. – vkolesnik Dec 11 '20 at 14:08

2 Answers2

0

To answer your specific scenario here, your extension method doesn't need the 2 generic parameters. TType isn't needed since TType and TValue in your case will always be the same. Just get rid of the TType argument and pass typeof(TValue) into the method like below

public static TValue GetValue<TValue>(this ITypedValueProvider<TValue> valueProvider)
{
    return valueProvider.GetValue(typeof(TValue));
}

C# can't infer TType, because from it can tell it is a completed unrelated type.

TValue is what is the type argument for ITypedValueProvider and the return type.. TType is just a 'stand alone' type argument, hence why you need to specify it.

But like I said, you clearly want TType and TValue to be the same type, so don't bother with TType, all that does is make you need to specify what feels like an unneeded type argument and leaves you open to bugs like

ITypedValueProvider<string> valueProvider = GetValueProvider();
valueProvider.GetValue<int>(); //what would this end up doing, definately not what you want though
Dave
  • 2,829
  • 3
  • 17
  • 44
0

I had a similar use case and this is what worked for me. There might be something you can use. What you would do is make a extension for ITypedValueProvider and infer the generic type of the caller from this inside the extension method.

The interface doesn't need to be generic:

public interface ITypedValueProvider
{
    TValue GetValue<TValue>();
}

You say you want a generic class of a different T. Here you go:

class TargetValueBase<TClass> : TargetValueBase, ITypedValueProvider { }

Make an instance of it:

var consumer = new TargetValueConsumer<AppDomain>();

So that you can call (for example):

consumer.LogValueForClass<XElement>()

Where a sample extension method (something you can't call on the class directly) might be something like:

public static class Extensions
{
    public static string LogValueForClass<TValue>(this ITypedValueProvider @this)
    {
        var type = @this.GetType();
        var builder = new List<string>();
        builder.Add($"INFERRED CLASS: { type.GetGenericTypeDefinition().Name.Split('`')[0]}" );
        foreach (var arg in type.GetGenericArguments())
        {
            builder.Add(arg.Name);
        }
        // Call a generic method on the provider
        var getValueResult = @this.GetValue<TValue>();

        builder.Add($"The GetValue method returned: {getValueResult}");
        return string.Join(Environment.NewLine, builder);
    }
}

The TargetValueBase doesn't need to be generic, either.

class TargetValueBase
{
    public TValue GetValue<TValue>() 
    {
        var tValueName = typeof(TValue).Name;

        // Figure out what to return.
        switch (tValueName)
        {
            case nameof(XElement):
                Console.WriteLine($"Case 1: {tValueName}");
                var instance = new XElement("xelement", new XAttribute("hello", "world"));
                return (TValue)(object)instance;
            case nameof(UInt16):
                Console.WriteLine($"Case 2: {tValueName}");
                break;
            case nameof(UInt32):
                Console.WriteLine($"Case 3: {tValueName}");
                break;
            case nameof(UInt64):
                Console.WriteLine($"Case 4: {tValueName}");
                break;
        }               
        return default(TValue);
    }
    public override string ToString() => GetType().Name;
}

Put it all in a little Unit Test...

[TestMethod]
public void GenericGetterTest()
{
    string result;
    TargetValueBase nonGeneric = new TargetValueBase();

    // Calling Generic GetValue doesn't require an extension
    // or even a generic class. But that's not what you asked.
    Console.WriteLine($"{Environment.NewLine}Calling Generic Method on Non-Generic Value Provider{Environment.NewLine}");
    nonGeneric.GetValue<XElement>();
    nonGeneric.GetValue<UInt16>();
    nonGeneric.GetValue<UInt32>();
    nonGeneric.GetValue<UInt64>();

    var retail = new TargetValueRetailer<CrossAppDomainDelegate>();
    var consumer = new TargetValueConsumer<AppDomain>();

    // We make extensions for methods we CAN'T call on the class. Maybe
    // the original class is from an external dll, or the class is
    // sealed for inheritance, or change all the references is prohibitive.

    // What you asked:
    // "Generic extension method with one generic type for class and another for value."
    // Here's a made-up one: 
    Console.WriteLine($"{Environment.NewLine}Calling Extension...{Environment.NewLine}");
    result = consumer.LogValueForClass<UInt16>();
    Console.WriteLine($"{result}{Environment.NewLine}");

    Console.WriteLine($"{Environment.NewLine}Calling Extension...{Environment.NewLine}");
    result = retail.LogValueForClass<XElement>();
    Console.WriteLine($"{retail.LogValueForClass<XElement>()}{Environment.NewLine}");
}

... and run it!

Console output

Fun.

IVSoftware
  • 5,732
  • 2
  • 12
  • 23