【问题标题】:Handling interactions between generic and static types处理泛型和静态类型之间的交互
【发布时间】:2013-05-30 05:12:55
【问题描述】:

我正在编写一个泛型方法,我希望根据泛型 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方法的诡计在于,具有不同类型参数的泛型静态类实际上被认为是不同的类型,并且不共享静态成员。

非常酷的东西,谢谢!

【问题讨论】:

  • 请注意区别,在我的示例中,对 Configure 的调用是静态的并且是全局应用的。这会减少代码,并意味着您不必为每个实例设置每个解析器。但是,它基于一个假设,即实例不会在不同的上下文中以不同的方式解析相同的值类型(这可能适用于您的用例,也可能不适用)。一个理想的解决方案可能是混合的:它会默认为本地(实例范围)配置,首先回退到全局/静态配置,作为最后的手段,回退到 default(T)。
  • @nawfal,类似,解决关键核心原则(泛型类型推断),但我认为我的问题来自不同的用例,我们不是将泛型类型转换为已知类型,而是将已知类型(字符串)重新转换为任意泛型类型(T)。
  • @Martaver 哦,是的,我错过了通用部分。

标签: c# generics


【解决方案1】:

除非您可以使用强类型键控集合(例如Dictionary&lt;string,int&gt;),否则您必须将值装箱。没有解决办法。

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

另外,您对dynamic 的看法是正确的。

更新

如果没有多种可能的值类型,那么使用字典的想法将适用。但是,如果只有 1 种可能的值类型(例如 int),那么您可以使用 Dictionary&lt;string,int&gt; 而不是 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);      
       }

}

【讨论】:

  • 我的方法的目标是能够为从 NameValueCollection 获得的各种类型包装解析代码。在现实世界的应用程序中,它将从类型列表中进行选择,为每种类型执行不同的解析代码。我将更新问题中的代码以更好地证明这一点。你认为你可以用字典来扩展你的意思吗?
  • 巧妙地使用了泛型静态类!我完全忘记了这个把戏。您能否详细说明为什么选择 ConcurrentDictionary 而不是常规字典?
  • @Martaver - 2 个原因,1. 一个值注册表每个具体值类型应该只有一个提供者。使用 ConcurrentDictionary 可确保在多线程环境中保持这种不变性。这就像您在创建单例实例之前检查 null、锁定、然后再次检查 null 的方式。 2. GetOrAdd 语法更简洁,但如果不是因为#1,您可以使用扩展方法来实现相同的目标。
【解决方案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));

【讨论】:

  • 赞成这一点,因为它是使用动态类型基于类型调度功能的一种非常好的方式。我也喜欢它动态处理相同类型的参数。然而,我最终选择了 smartcaveman 的答案,因为它纯粹是通用的,并且非常优雅地实现了同样的事情。
【解决方案3】:

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

return (T) Convert.ChangeType(GetInt32(collection, name), typeof (int));

【讨论】:

  • 很高兴了解 Convert.ChangeType,我一直想知道明确使用 IConvertible 接口的最佳方式是什么。不幸的是,ChangeType 的参数最终只是将 GetInt32 的返回结果装箱,所以如果我们使用它,我们最终会为相同的结果做更多​​的工作。
【解决方案4】:

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

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

【讨论】:

    猜你喜欢
    • 2010-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多