简体   繁体   English

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

[英]C#: Dynamic parse from System.Type

I have a Type, a String and an Object.我有一个类型、一个字符串和一个对象。

Is there some way I can call the parse method or convert for that type on the string dynamically?有什么方法可以在字符串上动态调用解析方法或转换为该类型?

Basically how do I remove the if statements in this logic基本上如何删除此逻辑中的 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);
}

And do someting more like this.并做更多这样的事情。

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


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

TypeDescriptor to the rescue!: TypeDescriptor来救援!:

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

All primitive types (plus Nullable<TPrimitive> , and numerous other built-in types) are integrated into the TypeConverter infrastructure already, and are thus supported 'out-of-the-box'.所有原始类型(加上Nullable<TPrimitive>和许多其他内置类型)都已经集成到 TypeConverter 基础结构中,因此“开箱即用”支持。

To integrate a custom type into the TypeConverter infrastructure, implement your own TypeConverter and use TypeConverterAttribute to decorate the class to be converted, with your new TypeConverter要将自定义类型集成到TypeConverter基础结构中,请实现您自己的TypeConverter并使用TypeConverterAttribute使用您的新TypeConverter来装饰要转换的类

This should work for all primitive types, and for types that implement IConvertible这应该适用于所有原始类型,以及实现IConvertible类型

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

EDIT : actually in your case, you can't use generics (not easily at least).编辑:实际上在你的情况下,你不能使用泛型(至少不容易)。 Instead you could do that :相反,您可以这样做:

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

I ran into this problem and this is how I solved it:我遇到了这个问题,这就是我解决它的方法:

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

...and it worked for me. ...它对我有用。

To sum it up, you are trying to find a static "Parse" method on the object type that takes only one string as an argument.总而言之,您正在尝试在仅接受一个字符串作为参数的对象类型上找到一个静态“解析”方法。 If you find such a method, then invoke it with the string parameter you are trying to convert.如果您找到这样的方法,则使用您尝试转换的字符串参数调用它。 Since p is the PropertyInfo for my type, I ended this method by setting my instance with the value as follows:由于 p 是我的类型的 PropertyInfo,因此我通过使用以下值设置我的实例来结束此方法:

p.SetValue(instance, value, null);

Depends on what you would like to accomplish.取决于你想完成什么。

1) if you are simply trying to clean up your code, and remove repetitive type checking, then what you want to do is centralize your checks in a method, comme 1)如果你只是想清理你的代码,并删除重复的类型检查,那么你想要做的是将你的检查集中在一个方法中,comme

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) if you would like something more generalized for basic types, you could try something like Thomas Levesque suggests - though in truth, I have not attempted this myself, I am unfamiliar with [recent?] extensions to Convert . 2)如果你想要更通用的基本类型,你可以尝试像Thomas Levesque 建议的那样- 虽然实际上,我自己没有尝试过,我不熟悉 [recent?] 对Convert扩展。 Also a very good suggestion.也是一个非常好的建议。

3) in fact, you probably want to merge both 1) and 2) above into a single extension that would enable you to support basic value conversion, and explicit complex type support. 3) 实际上,您可能希望将上面的 1) 和 2) 合并为一个扩展,使您能够支持基本值转换和显式复杂类型支持。

4) if you want to be completely "hands-free", then you could also default to plain old Deserialization [Xml or Binary, either/or]. 4)如果你想完全“免提”,那么你也可以默认为普通的反序列化[Xml或二进制,要么/或]。 Of course, this constrains your input - ie all input must be in an approriate Xml or Binary format.当然,这会限制您的输入 - 即所有输入必须采用适当的 Xml 或二进制格式。 Honestly, this is probably overkill, but worth mentioning.老实说,这可能有点矫枉过正,但值得一提。

Of course, all of these methods do essentially the same thing.当然,所有这些方法基本上都做同样的事情。 There is no magic in any of them, at some point someone is performing a linear lookup [whether it is an implicit look up through sequential if-clauses or under the hood via .Net conversion and serialization facilities].它们中的任何一个都没有魔法,在某些时候有人正在执行线性查找 [无论是通过顺序 if 子句进行隐式查找,还是通过 .Net 转换和序列化工具在幕后进行查找]。

5) if you want to improve performance, then what you want to do is improve the "lookup" part of your conversion process. 5)如果您想提高性能,那么您要做的就是改进转换过程的“查找”部分。 Create an explicit "supported types" list, each type corresponding to an index in an array.创建一个明确的“支持的类型”列表,每个类型对应于数组中的一个索引。 Instead of specifying the Type on a call, you then specify the index.然后指定索引,而不是在调用时指定类型。

EDIT: so, while linear look up is neat and speedy, it also occurs to me it would be even faster if consumer simply obtained conversion functions and invoked them directly.编辑:因此,虽然线性查找既简洁又快速,但我也想到,如果消费者简单地获取转换函数并直接调用它们会更快。 That is, consumer knows what type it would like to convert to [this is a given], so if it needs to convert many items at one time,也就是说,消费者知道它想转换成什么类型​​[这是一个给定的],所以如果它需要一次转换很多项,

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

}

and it would be used as它将被用作

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

I much prefer this latter approach, because it gives you complete control over conversion.我更喜欢后一种方法,因为它可以让您完全控制转换。 If you use an Inversion of Control [IoC] container like Castle Windsor or Unity, then injection of this service is done for you.如果您使用诸如 Castle Windsor 或 Unity 之类的控制反转 [IoC] 容器,则此服务的注入已为您完成。 Also, because it is instance based, you can have multiple instances, each with its own set of conversion rules - if for instance, you have multiple user controls, each generating it's own DateTime or other complex string format.此外,因为它是基于实例的,所以您可以拥有多个实例,每个实例都有自己的一组转换规则——例如,如果您有多个用户控件,每个控件都生成自己的DateTime或其他复杂的字符串格式。

Heck, even if you wanted to support multiple conversion rules for a single target type, that is also possible, you simply have to extend method parameters to specify which one.哎呀,即使您想支持单个目标类型的多个转换规则,这也是可能的,您只需扩展方法参数以指定哪个。

It's technically impossible to look at a string, and know for certain which type it represents.从技术上讲,查看字符串并确定它代表哪种类型是不可能的。

So, for any generic approach, you'll need at least:因此,对于任何通用方法,您至少需要:

  1. the string to be parsed要解析的字符串
  2. the type used for parsing.用于解析的类型。

Take a look at the static Convert.ChangeType() method.看看静态Convert.ChangeType()方法。

It seems like what you want to do (at least if the types involved are types to which you can't modify the source) would require duck typing which isn't in C#看起来您想要做的事情(至少如果所涉及的类型是您无法修改源的类型)需要鸭子类型,而 C# 中没有

If you need to do this a lot, I would wrap the logic in a class or method that you can pass "myString" and "propType" to and it would return value.如果您需要经常这样做,我会将逻辑包装在一个类或方法中,您可以将“myString”和“propType”传递给它,它会返回值。 In that method you'd just do the if chain you have above and return the value when it finds one that matches.在该方法中,您只需执行上面的 if 链,并在找到匹配的值时返回该值。 You'd have to manually list out all the possible types still, but you'd only have to do it once.您仍然必须手动列出所有可能的类型,但您只需要做一次。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM