Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get from a type to the TryParse method?

Tags:

c#

types

parsing

My particular problem:

I have a string which specifies an aribitrary type in a configuration class

Config.numberType = "System.Foo";

where Foo is a type like Decimal or Double

I use Type.GetType(Config.numberType) to return the corresponding type.

How do I get from that type to being able to use, System.Foo.TryParse() ?

Some further related queries

  • TryParse() can be accessed from System.Foo.TryParse() as well as foo.TryParse(). Does this mean foo is some kind of class in C#? This seems weird to me that int, double etc are actually not just modifier keywords.
  • How can you declare variables under these circumstances? - var is not universally usable it seems i.e. only in local scope etc.
like image 543
Brendan Avatar asked Jan 24 '09 20:01

Brendan


3 Answers

As many have said - there isn't a direct route. I expect one close option is TypeConverter:

    Type type = typeof(double);
    string text = "123.45";

    object value = TypeDescriptor.GetConverter(type)
        .ConvertFromInvariantString(text);

Of course, you may need try/catch to handle exceptions. Such is life.

like image 121
Marc Gravell Avatar answered Nov 12 '22 16:11

Marc Gravell


How do I get from that type to being able to use, System.Foo.TryParse() ?

You'll need to use reflection to look up and then invoke the static TryParse() method. Not all types implement this method - so you'll have to decide how to handle it if it's missing. You could also use System.Convert to convert a string to an arbitrary type, assuming the string is actually a valid representation of a value for that type and there's a conversion implemented for it.

TryParse() can be accessed from System.Foo.TryParse() as well as foo.TryParse(). Does this mean foo is some kind of class in C#?

int, double, etc. are aliases for System.Int32, System.Double, etc. - they're part of the C# language, which would be uncomfortably verbose without them.

How can you declare variables under these circumstances?

Without knowing the type at compile-time, you'll be forced to declare and work with your data as object / System.Object. C# 4.0 will introduce actual dynamic types that will take care of some of the tedious reflection work for you, but for now you're stuck doing it by hand. Note that if you use System.Convert in a method with a parametrized type argument and return, or TryParse() using a technique such as that linked to by Sebastian Sedlak, you can easily achieve the ability to write client code that works with static types... So long as they match or can be converted to from the types you're parsing.

like image 34
Shog9 Avatar answered Nov 12 '22 14:11

Shog9


EDITED: I removed the generic implementation and cleaned up this response to fit better to the originally stated problem.

NOTE: The answer by Marc Gravell is probably the most concise if you just want the parsed value given the type. The answer below shows you how to get at the method (i.e., the MethodInfo object and how to invoke it).

The following should work, at least for types that implement public static bool TryParse(string, T value):

public static class Parsing
{
    static MethodInfo findTryParseMethod(Type type)
    {
        //find member of type with signature 'static public bool TryParse(string, out T)'
        BindingFlags access = BindingFlags.Static | BindingFlags.Public;
        MemberInfo[] candidates = type.FindMembers(
            MemberTypes.Method,
            access,
            delegate(MemberInfo m, object o_ignored)
            {
                MethodInfo method = (MethodInfo)m;
                if (method.Name != "TryParse") return false;
                if (method.ReturnParameter.ParameterType != typeof(bool)) return false;
                ParameterInfo[] parms = method.GetParameters();
                if (parms.Length != 2) return false;
                if (parms[0].ParameterType != typeof(string)) return false;
                if (parms[1].ParameterType != type.MakeByRefType()) return false;
                if (!parms[1].IsOut) return false;

                return true;

            }, null);

        if (candidates.Length > 1)
        {
            //change this to your favorite exception or use an assertion
            throw new System.Exception(String.Format(
                "Found more than one method with signature 'public static bool TryParse(string, out {0})' in type {0}.",
                type));
        }
        if (candidates.Length == 0)
        {
            //This type does not contain a TryParse method - replace this by your error handling of choice
            throw new System.Exception(String.Format(
                "Found no method with signature 'public static bool TryParse(string, out {0})' in type {0}.",
                type));
        }
        return (MethodInfo)candidates[0];
    }

    public static bool TryParse(Type t, string s, out object val)
    {
        MethodInfo method = findTryParseMethod(t); //can also cache 'method' in a Dictionary<Type, MethodInfo> if desired
        object[] oArgs = new object[] { s, null };
        bool bRes = (bool)method.Invoke(null, oArgs);
        val = oArgs[1];
        return bRes;
    }

    //if you want to use TryParse in a generic syntax:
    public static bool TryParseGeneric<T>(string s, out T val)
    {
        object oVal;
        bool bRes = TryParse(typeof(T), s, out oVal);
        val = (T)oVal;
        return bRes;
    }
}

Use the following test code:

        public bool test()
    {
        try
        {
            object oVal;
            bool b = Parsing.TryParse(typeof(int), "123", out oVal);
            if (!b) return false;
            int x = (int)oVal;
            if (x!= 123) return false;
        }
        catch (System.Exception)
        {
            return false;
        }

        try
        {
            int x;
            bool b = Parsing.TryParseGeneric<int>("123", out x);
            if (!b) return false;
            if (x != 123) return false;
        }
        catch (System.Exception)
        {
            return false;
        }


        try
        {
            object oVal;
            bool b = Parsing.TryParse(typeof(string), "123", out oVal);
            //should throw an exception (//no method String.TryParse(string s, out string val)
            return false;
        }
        catch (System.Exception)
        {
            //should throw an exception
        }

        return true;
    }
}

And use this in your case:

    //input: string s, Config
Type tNum = Type.GetType(Config.numberType);    
object oVal;
bool ok = Parsing.TryParse(tNum, s, out oVal);
//oVal is now of type tNum and its value is properly defined if ok == true

About the use of var: you may have a misconception of what var does: It is not a "variant" type (the type object already is used for that) but moves the declaration syntax for the type to the assignment's right side. The following declarations are equivalent:

var i = 1;  //the compiler infers the type from the assignment, type of i is int.
int i = 1;  //type of i is int via declaration

The primary use of var is allowing to create anonymous types:

var anon = new { Name = "abc", X = 123 };
like image 5
ppp Avatar answered Nov 12 '22 15:11

ppp