C#:来自System.Type的动态解析

时间:2010-03-04 15:30:01

标签: c# .net type-conversion

我有一个Type,一个String和一个Object。

我是否可以通过某种方式动态调用解析方法或在字符串上转换该类型?

基本上如何删除此逻辑中的if语句

object value = new object();    
String myString = "something";
Type propType = p.PropertyType;

if(propType == Type.GetType("DateTime"))
{
    value = DateTime.Parse(myString);
}

if (propType == Type.GetType("int"))
{
    value = int.Parse(myString);
}

并做更多这样的事情。

object value = new object();
String myString = "something";
Type propType = p.PropertyType;


//this doesn't actually work
value = propType .Parse(myString);  

6 个答案:

答案 0 :(得分:77)

TypeDescriptor拯救:

var converter = TypeDescriptor.GetConverter(propType);
var result = converter.ConvertFrom(myString);

要集成到TypeConverter基础架构中,请使用TypeConverter

实现您自己的TypeConverterAttribute和装饰类。

答案 1 :(得分:15)

这适用于所有原始类型,以及实现IConvertible

的类型
public static T ConvertTo<T>(object value)
{
    return (T)Convert.ChangeType(value, typeof(T));
}

编辑:实际上在你的情况下,你不能使用泛型(至少不容易)。相反,你可以这样做:

object value = Convert.ChangeType(myString, propType);

答案 2 :(得分:7)

我遇到了这个问题,这就是我解决它的方法:

value = myString;
var parse = propType.GetMethod("Parse", new[] { typeof(string) });
if (parse != null) {
  value = parse.Invoke(null, new object[] { value });
}

......它对我有用。

总结一下,您试图在对象类型上找到一个静态“Parse”方法,该方法只接受一个字符串作为参数。如果找到这样的方法,则使用您尝试转换的字符串参数调用它。由于p是我的类型的PropertyInfo,我通过使用以下值设置我的实例来结束此方法:

p.SetValue(instance, value, null);

答案 3 :(得分:2)

取决于您想要完成的任务。

1)如果您只是想清理代码并删除重复类型检查,那么您想要做的是将检查集中在一个方法中,纪念

public static T To<T> (this string stringValue)
{
    T value = default (T);

    if (typeof (T) == typeof (DateTime))
    {
        // insert custom or convention System.DateTime 
        // deserialization here ...
    }
    // ... add other explicit support here
    else
    {
        throw new NotSupportedException (
            string.Format (
            "Cannot convert type [{0}] with value [{1}] to type [{2}]." + 
            " [{2}] is not supported.",
            stringValue.GetType (),
            stringValue,
            typeof (T)));
    }

    return value;
}

2)如果你想要更基本类型的东西,你可以尝试类似Thomas Levesque suggests的东西 - 虽然事实上,我自己没有尝试过,我不熟悉[最近? ] Convert的扩展。也是一个非常好的建议。

事实上,您可能希望将上述1)和2)合并为一个扩展,使您能够支持基本值转换和显式复杂类型支持。

4)如果你想完全“免提”,那么你也可以默认使用普通的旧反序列化[Xml或二进制,或者/或]。当然,这会限制您的输入 - 即所有输入必须采用适当的Xml或二进制格式。老实说,这可能是矫枉过正,但值得一提。

当然,所有这些方法基本上都是一样的。其中任何一个都没有任何魔力,在某些时候某人正在执行线性查找[无论是通过顺序if-clauses进行隐式查找还是通过.Net转换和序列化工具进行查找]

5)如果您想提高性能,那么您想要做的就是改进转换过程中的“查找”部分。创建一个显式的“支持的类型”列表,每个类型对应一个数组中的索引。然后,您可以指定索引,而不是在调用中指定Type。

编辑:所以,虽然线性查找是整洁而快速的,但对我来说,如果消费者只是简单地获得转换函数并直接调用它们,它也会更快。也就是说,消费者知道它想要转换为[这是给定]的类型,所以如果它需要一次转换多个项目,

// S == source type
// T == target type
public interface IConvert<S>
{
    // consumers\infrastructure may now add support
    int AddConversion<T> (Func<S, T> conversion);

    // gets conversion method for local consumption
    Func<S, T> GetConversion<T> ();

    // easy to use, linear look up for one-off conversions
    T To<T> (S value);
}

public class Convert<S> : IConvert<S>
{

    private class ConversionRule
    {
        public Type SupportedType { get; set; }
        public Func<S, object> Conversion { get; set; }
    }

    private readonly List<ConversionRule> _map = new List<ConversionRule> ();
    private readonly object _syncRoot = new object ();

    public void AddConversion<T> (Func<S, T> conversion)
    {
        lock (_syncRoot)
        {
            if (_map.Any (c => c.SupportedType.Equals (typeof (T))))
            {
                throw new ArgumentException (
                    string.Format (
                    "Conversion from [{0}] to [{1}] already exists. " +
                    "Cannot add new conversion.", 
                    typeof (S), 
                    typeof (T)));
            }

            ConversionRule conversionRule = new ConversionRule
            {
                SupportedType = typeof(T),
                Conversion = (s) => conversion (s),
            };
            _map.Add (conversionRule);
        }
    }

    public Func<S, T> GetConversion<T> ()
    {
        Func<S, T> conversionMethod = null;

        lock (_syncRoot)
        {
            ConversionRule conversion = _map.
                SingleOrDefault (c => c.SupportedType.Equals (typeof (T)));

            if (conversion == null)
            {
                throw new NotSupportedException (
                    string.Format (
                    "Conversion from [{0}] to [{1}] is not supported. " + 
                    "Cannot get conversion.", 
                    typeof (S), 
                    typeof (T)));
            }

            conversionMethod = 
                (value) => ConvertWrap<T> (conversion.Conversion, value);
        }

        return conversionMethod;
    }

    public T To<T> (S value)
    {
        Func<S, T> conversion = GetConversion<T> ();
        T typedValue = conversion (value);
        return typedValue;
    }

    // private methods

    private T ConvertWrap<T> (Func<S, object> conversion, S value)
    {
        object untypedValue = null;
        try
        {
            untypedValue = conversion (value);
        }
        catch (Exception exception)
        {
            throw new ArgumentException (
                string.Format (
                "Unexpected exception encountered during conversion. " +
                "Cannot convert [{0}] [{1}] to [{2}].",
                typeof (S),
                value,
                typeof (T)),
                exception);
        }

        if (!(untypedValue is T))
        {
            throw new InvalidCastException (
                string.Format (
                "Converted [{0}] [{1}] to [{2}] [{3}], " +
                "not of expected type [{4}]. Conversion failed.",
                typeof (S),
                value,
                untypedValue.GetType (),
                untypedValue,
                typeof (T)));
        }

        T typedValue = (T)(untypedValue);

        return typedValue;
    }

}

它将用作

// as part of application innitialization
IConvert<string> stringConverter = container.Resolve<IConvert<string>> ();
stringConverter.AddConversion<int> (s => Convert.ToInt32 (s));
stringConverter.AddConversion<Color> (s => CustomColorParser (s));

...

// a consumer elsewhere in code, say a Command acting on 
// string input fields of a form
// 
// NOTE: stringConverter could be injected as part of DI
// framework, or obtained directly from IoC container as above
int someCount = stringConverter.To<int> (someCountString);

Func<string, Color> ToColor = stringConverter.GetConversion <Color> ();
IEnumerable<Color> colors = colorStrings.Select (s => ToColor (s));

我更喜欢后一种方法,因为它为您提供完全控制转换。如果您使用Castle Windsor或Unity等Inversion of Control [IoC]容器,则会为您完成此服务的注入。此外,因为它是基于实例的,所以您可以拥有多个实例,每个实例都有自己的一组转换规则 - 例如,如果您有多个用户控件,每个都生成自己的DateTime或者其他复杂的字符串格式。

哎呀,即使你想支持单个目标类型的多个转换规则,这也是可能的,你只需要扩展方法参数来指定哪一个。

答案 4 :(得分:0)

从技术上讲,查看字符串是不可能的,并且确定它代表了哪种类型。

因此,对于任何通用方法,至少需要:

  1. 要解析的字符串
  2. 用于解析的类型。
  3. 查看静态Convert.ChangeType()方法。

答案 5 :(得分:-1)

看起来你想要做的事情(至少如果所涉及的类型是你无法修改源的类型)需要duck typing而不是C#

如果你需要做很多事情,我会将逻辑包装在一个类或方法中,你可以将“myString”和“propType”传递给它,它会返回值。在该方法中,您只需执行上面的if链,并在找到匹配的值时返回该值。你必须手动列出所有可能的类型,但你只需要做一次。