【问题标题】:How can I convert to a specific type in a generic version of TryParse()?如何在 TryParse() 的泛型版本中转换为特定类型?
【发布时间】:2009-07-09 23:33:31
【问题描述】:

我有以下场景,我想传入字符串和泛型类型:

public class Worker {
    public void DoSomeWork<T>(string value) 
        where T : struct, IComparable<T>, IEquatable<T> { ... }
}

在某些时候,我需要将字符串值转换为其T 值。但我不想直接转换,因为如果字符串无法转换为类型T,我需要执行一些逻辑。

我在想我可以尝试使用 Convert.ChangeType() 但这有一个问题,如果它不转换它会抛出异常,我将经常运行 DoSomeWork() 方法,而不必依赖try/catch 判断转换是否有效。

所以这让我想到,我知道我将使用数字类型,因此 T 将是以下任何一种:intuintshortushortlong、@ 987654331@、bytesbytedecimalfloatdouble。知道这一点后,我认为可能会提出一个更快的解决方案,因为我知道我将使用数字类型(请注意,如果 T 不是数字类型,我会抛出异常)...

public class NumericWorker {
    public void DoSomeWork<T>(string value) 
        where T : struct, IComparable<T>, IEquatable<T> 
    { 
        ParseDelegate<T> tryConverter = 
           SafeConvert.RetreiveNumericTryParseDelegate<T>();
        ... 
    }
}


public class SafeConvert
{
    public delegate bool ParseDelegate<T>(string value, out T result);

    public static ParseDelegate<T> RetreiveNumericTryParseDelegate<T>()
        where T : struct, IComparable<T>, IEquatable<T>
    {
        ParseDelegate<T> tryParseDelegate = null;

        if (typeof(T) == typeof(int))
        {
           tryParseDelegate = (string v, out T t) =>
              {
                 int typedValue; 
                 bool result = int.TryParse(v, out typedValue);
                 t = result ? (T)typedValue : default(T); 
                 //(T)Convert.ChangeType(typedValue, typeof(T)) : default(T);
                 return result;
              }; 
        }
        else if (typeof(T) == typeof(uint)) { ... }
        else if (typeof(T) == typeof(short)) { ... }
        else if (typeof(T) == typeof(ushort)) { ... }
        else if (typeof(T) == typeof(long)) { ... }
        else if (typeof(T) == typeof(ulong)) { ... }
        else if (typeof(T) == typeof(byte)) { ... }
        else if (typeof(T) == typeof(sbyte)) { ... }
        else if (typeof(T) == typeof(decimal)) { ... }
        else if (typeof(T) == typeof(float)) { ... }
        else if (typeof(T) == typeof(double)) { ... }

        return tryParseDelegate;
    }
}

但是上面的问题是我不能写t = result ? (T)typedValue : default(T);,因为typedValueT 的转换会导致问题,到目前为止我能够解决它的唯一方法是写@987654342 @。但如果我这样做,我只是在做另一个转换。

因此我想知道是否有人知道我可以如何解决这个问题(如果您认为执行 ChangeType() 是一个问题),或者是否有更好的解决方案我没有考虑过。

【问题讨论】:

    标签: c# .net generics casting


    【解决方案1】:

    t = 结果? (T)typedValue : 默认值(T);

    试试:

    t = result ? (T)(object)typedValue : default(T);
    

    是的,泛型有时会有点烦人。

    FWIW,我在Convert.ChangeType() 周围使用a much simpler wrapper,它只是对空字符串进行预检查。除非您将其用于未经检查的用户输入,否则可能就足够了。

    【讨论】:

    • 我使用它的目的是验证用户输入... :( 这就是为什么我不想使用 changetype 和 try/catch 因为谁知道用户会输入什么...
    • 嘿,好的。 :-) FWIW,我不会太担心捕获ChangeType() 抛出的异常的效率方面,但是如果您担心样式,那么您的解决方案看起来不错。请注意,您可以通过将 API 简化为单个泛型方法来简化 API,其中 T 由 out 参数隐式指定。
    • 为了简单起见,你说的“其中 T 由 out 参数隐式指定”是什么意思??
    • 你的意思是在委托内部进行类型测试...如果是这样,我这样做的原因是为了性能,这意味着一旦我进行测试并返回一个委托,我就可以运行尽可能多地委托,而性能副作用最小......
    • 啊,明白了 - 想知道代表的意义是什么。希望能帮助到你! :-)
    【解决方案2】:

    鉴于此:

    因此 T 将是以下任何一种:int、uint、short、ushort、long、ulong、byte、sbyte、decimal、float、double。

    我建议只使用 Convert.ChangeType,而不用担心它。唯一出现异常的情况是您的字符串格式错误,在这种情况下,您可以返回 default(T)。

    即:

    try
    {
        result = Convert.ChangeType(value, typeof(T));
    }
    catch
    {
        result = default(T);
    }
    

    【讨论】:

    • 正如我给@Shog9 的评论中提到的那样,问题是因为我使用它来验证用户输入我担心生成异常的开销。 afaik 生成一个异常,您知道每次运行该方法时可能会发生一次异常,这是一项昂贵的操作,因此我尝试改用委托。考虑到您建议的我尝试使用的委托方法的版本仍然值得使用,您会说吗?
    • 就个人而言,我仍然会使用它。在这种情况下,我不会担心异常的开销,特别是因为如果它用于用户输入,您不需要在紧密循环中调用它(因为用户可能无法像您可以处理异常)。即使使用其他方法,您的 switch 语句、委托调用等也会有开销,与我上面的 6 行相比,维护起来很糟糕。
    • 我同意里德的观点。异常在最坏的情况下(冷)可能需要几十毫秒的时间;但如果您正在验证 one 用户输入字符串,那几乎不会引起注意。
    • 结果 = (T)Convert.ChangeType(value, typeof(T));您还必须从 ChangeType 进行转换。
    【解决方案3】:

    ToType 是这里的通用参数。这适用于可为空的类型,以防万一您需要它。您可以将您的主要方法提取为通用转换器,它将转换为任何类型,包括可为空的。

        ToType result = default(ToType);    
    
        result = ChangeType<ToType>(typedValue);
    
    
      private T ChangeType<T>(object o)
    {
       Type conversionType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
       return (T)Convert.ChangeType(o, conversionType);
    }
    

    【讨论】:

    • 如果我使用这种方法,这如何解决对 try/catch 块的需求??
    • 不会抛出异常,因为您使用的是 TryParse。如果 TryParse 的结果为真,那么除非您传入完全不同的通用参数,否则转换将起作用。这是你的困境吗?
    • 抱歉,您不能传递不同的泛型参数,因为您有一个关于 typeof(T) 的 if 语句......那么您的问题到底是什么?
    【解决方案4】:

    你可以尝试一些简单的事情

        public static T ConvertValue<T,U>(U value) where U : IConvertible {
            return (T)ConvertValue(value, typeof(T));
        }
    
        public static object ConvertValue(IConvertible value, Type targetType) {
            return Convert.ChangeType(value, targetType);
        }
    

    【讨论】:

      【解决方案5】:

      为什么不直接使用反射并使用内置的 TryParse 方法?除了 Guid 之外,几乎每种原生类型都有一个。

      public static Parser<T> GetParser<T>(T defaultResult)
          where T : struct
      {
          // create parsing method
          Parser<T> parser = (string value, out T result) =>
          {
              // look for TryParse(string value,out T result)
              var parseMethod = 
                  typeof(T).GetMethods()
                           .Where(p => p.Name == "TryParse")
                           .Where(p => p.GetParameters().Length == 2)
                           .Single();
      
              // make parameters, leaving second element uninitialized means out/ref parameter
              object[] parameters = new object[2];
              parameters[0] = value;
      
              // run parse method
              bool success = (bool)parseMethod.Invoke(null, parameters);
      
              // if successful, set result to output
              if (!success)
              {
                  result = (T)parameters[1];
              }
              else
              {
                  result = defaultResult;
              }
      
              return success;
          };
      
          return parser;
      }
      

      【讨论】:

      • 如果您对其进行测试,您可以在 11 秒内完成超过 100,000 次解析。我使用耗时 14 秒的原生表达式树与 .net 4 进行了比较(尽管在 .net 4 发布时可能不会反映性能)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-01
      • 1970-01-01
      相关资源
      最近更新 更多