簡體   English   中英

如何為泛型 class 編寫具有一種類型參數和另一種類型參數的泛型擴展方法

[英]How to write generic extension method which with one type argument for generic class with another type argument

我有以下界面

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

我想創建擴展方法,它允許我從泛型類型參數中獲取類型。 我寫了一個如下的擴展方法。

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

但是,此擴展方法需要 2 個類型 arguments,我只需要使用一個,希望其他可以從輸入泛型 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 {}

我如何編寫不需要第二類型參數的擴展方法,或者如何使用現有方法從用法中推斷第二類型參數?

要在這里回答您的具體情況,您的擴展方法不需要 2 個通用參數。 不需要 TType ,因為在您的情況下 TType 和 TValue 將始終相同。 只需擺脫 TType 參數並將typeof(TValue)傳遞給如下方法

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

C# 無法推斷 TType,因為從中可以看出它是一個完整的無關類型。

TValue 是 ITypedValueProvider 的類型參數和返回類型。TType 只是一個“獨立”類型參數,因此您需要指定它。

但是就像我說的,你顯然希望 TType 和 TValue 是相同的類型,所以不要為 TType 煩惱,所做的只是讓你需要指定什么感覺像是一個不需要的類型參數,並且讓你容易遇到錯誤,比如

ITypedValueProvider<string> valueProvider = GetValueProvider();
valueProvider.GetValue<int>(); //what would this end up doing, definately not what you want though

我有一個類似的用例,這對我有用。 可能有一些你可以使用的東西。 您要做的是對ITypedValueProvider進行擴展,並在擴展this中從中推斷調用者的泛型類型。

接口不需要是通用的:

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

你說你想要一個不同 T 的通用 class。給你 go:

class TargetValueBase<TClass> : TargetValueBase, ITypedValueProvider { }

創建一個實例:

var consumer = new TargetValueConsumer<AppDomain>();

這樣您就可以調用(例如):

consumer.LogValueForClass<XElement>()

示例擴展方法(您不能直接在 class 上調用的方法)可能類似於:

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);
    }
}

TargetValueBase也不需要是通用的。

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;
}

把它全部放在一個小的單元測試中......

[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}");
}

...然后運行它!

控制台輸出

樂趣。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM