1

我正在编写一个泛型方法,我希望根据泛型 Type 参数的类型出现不同的执行路径。不同的执行路径是静态类型的,例如

public static T Get<T>(this NameValueCollection collection, string name) where T : struct
{
    //Perform test on type, if it matches, delegate to statically typed method.
    if (typeof(T) == typeof(int)) return (T)(object)GetInt32(collection, name);
    else if (typeof(T) == typeof(DateTime)) return (T) (object) GetDateTime(collection, name);

    //Other types parsed here...

    //If nothing matched, return default.
    return default(T);
}

我发现能够使用静态执行路径的返回结果的唯一方法是将其装箱为对象,然后将其转换为“T”。

对我来说,这首先违背了使用泛型方法的目的(除了获得一些语法糖)。在我们已经确定 T 是 int 类型的情况下,有谁知道一种能够将 int 值返回为 T 的方法?

我正在考虑使用“动态”类型的 var,但读到它只是在幕后使用对象结束。

这也许是泛型的范围吗?

--

更新以包括我的最终方法,基于 smartcaveman 的回复,它使用通用静态类的类型解析来确定使用哪种“解析”方法,无需装箱或使用动态。

public class StringParser
{
    private class Getter<T>
    {
        private static readonly ConcurrentDictionary<StringParser, Getter<T>> Getters = new ConcurrentDictionary<StringParser, Getter<T>>();

        public Func<string, T> Get { get; set; }

        private Getter() {}

        public static Getter<T> For(StringParser stringParser)
        {
            return Getters.GetOrAdd(stringParser, x => new Getter<T>());
        }
    }

    public virtual T Get<T>(string value)
    {
        var get = Getter<T>.For(this).Get;

        if (get == null) throw new InvalidOperationException(string.Format("No 'get' has been configured for values of type '{0}'.", typeof (T).Name));

        return get(value);
    }

    public void SetupGet<T>(Func<string, T> get)
    {
        Getter<T>.For(this).Get = get;
    }
}

使用相当简单:

public static void Usage()
{
    StringParser parser = new StringParser();
    parser.SetupGet(Int32.Parse);            
    int myInt = parser.Get<int>("3");            
}

smartcaveman 方法的窍门在于,具有不同类型参数的泛型静态类实际上被认为是不同的类型,并且不共享静态成员。

很酷的东西,谢谢!

4

4 回答 4

2

使用 C# 4.0 中引入的动态特性(下一个版本也支持它),这种调度是可能的。

此代码执行您在此处表达的内容:

public static T Get<T>(this NameValueCollection collection, string name) where T : struct
{
    T v = default(T);
    dynamic indicator = v;

    return GetValue(collection, name, indicator);
}

static int GetValue(NameValueCollection collection, string name, int indicator)
{
    return 110;
}

static DateTime GetValue(NameValueCollection collection, string name, DateTime indicator)
{
    return DateTime.Now;
}

// ... other helper parsers

// if nothing else matched
static object GetValue(NameValueCollection collection, string name, object indicator)
{
    return indicator;
}

要执行冒烟测试:

Console.WriteLine(Get<int>(null, null));
Console.WriteLine(Get<DateTime>(null, null));
Console.WriteLine(Get<double>(null, null));
于 2013-05-30T07:46:25.700 回答
2

除非您可以使用强类型键控集合(例如Dictionary<string,int>),否则您将不得不对值进行装箱。没有解决方法。

话虽如此,尚不清楚您的方法如何比非通用版本有用。我没有看到调用代码,但似乎唯一相关的情况是 int,因为其他所有情况都只返回默认值。

另外,您对dynamic.

更新

如果没有多种可能的值类型,则使用字典的想法将适用。但是,如果只有 1 种可能的值类型(例如 int),那么您可以使用 aDictionary<string,int>而不是 a NameValueCollection

我写了一个小示例,它可能会为您提供有关如何使用自定义类保持强类型的想法。我省略了空值检查和参数验证逻辑,这既没有经过测试也没有编译。但是,基本思想应该很清楚。您可以按如下方式使用ValueRegistry该类。NameValueCollection

 //  around application start, configure the way values are retrieved   
 //  from names for different types.  Note that this doesn't need to use
 //  a NameValueCollection, but I did so to stay consistent.

 var collection = new NameValueCollection();
 ValueRegistry.Configure<int>(name => GetInt32(collection,name));
 ValueRegistry.Configure<DateTime>(name => GetDateTime(collection,name));

 // where you are going to need to get values
 var values = new ValueRegistry();      
 int value = values.Get<int>("the name"); // nothing is boxed

public class ValueRegistry
{
       private class Provider<T> 
            where T : struct
       {
             private static readonly ConcurrentDictionary<ValueRegistry,Provider<T>> Providers = new ConcurrentDictionary<ValueRegistry,Provider<T>>();
              public static Provider<T> For(ValueRegistry registry)
              {
                  return Providers.GetOrAdd(registry, x => new Provider<T>());
              }
              private Provider(){
                 this.entries = new Dictionary<string,T>();
              }
              private readonly Dictionary<string,T> entries;
              private static Func<string,T> CustomGetter;
              public static void Configure(Func<string,T> getter) { CustomGetter = getter;}

              public static T GetValueOrDefault(string name)
              {
                   T value;
                    if(!entries.TryGetValue(name, out value))
                       entries[name] = value = CustomGetter != null ? CustomGetter(name) : default(T);
                     return value;
              }
       }

       public T Get<T>(string name) 
          where T : struct
       {
           return Provider<T>.For(this).GetValueOrDefault(name);
       }

       public static void Configure<T>(Func<string,T> customGetter)
                  where T : struct
       {
          Provider<T>.Configure(customGetter);      
       }

}
于 2013-05-30T05:23:59.130 回答
1

是的,即使您已经声明,您也必须在将其转换为通用 T 之前显式地装箱(将值类型转换为对象)where T : struct。您可以执行以下操作,但我不能说它更优雅。

return (T) Convert.ChangeType(GetInt32(collection, name), typeof (int));
于 2013-05-30T05:27:10.347 回答
0

您可以使用 aDictionary来处理此问题。

private static Dictionary<Type, Func<NameValueCollection, string, T>> _typeMap = new Dictionary<Type, Func<NameValueCollection, string, T>>();

static Constructor()
{
    _typeMap[typeof(DateTime)] = (nvc, name) => { return (T)GetDateTime(nvc, name); };
    // etc
}


public static T Get<T>(this NameValueCollection collection, string name) where T : struct
{
    return _typeMap[typeof(T)](collection, name);
}
于 2013-05-30T07:54:10.057 回答