0

Imagine the following scenario:

A user passes a string to the application, which represents a .NET-Type, e.g. string or System.IntPtr. Assuming, that the application in question has access to the assemblies, in which the given Types are defined, it can create an instance of the given type based on the string.

I have managed to create such an application; the problem is only, that I am not (yet) able to create "complex" or "nested" types, e.g.:

  • Generics like System.Tuple<string, System.Action>
  • Arrays of T like int[], System.IntPtr[], etc...
  • Pointer of T like int*, char**, Namespace.MyStruct*, etc...
  • Any (possible) combination of the cases above

My question is: How can I create such nested type instances, when only given the string representation of the type (and of course all required assemblies)?

Do the .NET BCL contain some sort of string --> Type-parser? Or do I have to use some form of recursive regex-parser and Activator.CreateInstance?


EDIT №1:
As this question seems to be too broad, I will try to clarify myself:

I am able to create type instances for 'simple' types - I am, however, struggling to create type instances of 'complex' types, e.g.:

Type t1 = typeof(string).Assembly.GetType("System.String");
Type t2 = typeof(string).Assembly.GetType("System.Tuple<System.String, System.Int32>");
object obj1 = Activator.CreateInstance(t1);
object obj2 = Activator.CreateInstance(t2);

obj1 is an instance of string, but obj2 will fail, as the variable t2 is null (Meaning, that the second line in the given code was unable to find the 'complex' type).


EDIT №2:
The usage of Type::MakeGenericType(Type[]) is obviously helpful, but how would be the easiest/fastest(?) way to create an instance of a type like this:
IEnumerable<Tuple<int[,], char[][], Dictionary<string, Func<int, int*, byte>>>>
I would have to write some kind of regex-based parser, which iterates recursively over the different sections of the given string....


EDIT №3:
I have discovered System::CodeDom::CodeTypeReference and I am currently searching for a method to get the type based on a given CodeTypeReference-instance (which can be created using the type's string representation).


EDIT №4: (sorry for all the edits)
I think I found my solution in this SO post.

Community
  • 1
  • 1
unknown6656
  • 2,765
  • 2
  • 36
  • 52
  • 1
    I didn't vote to close this question because, although there may be tons of ways to do this (so it's Too Broad) there is also built-in BCL methods of doing it pretty standardly, so I think it's actually beneficial to SO to have a question about this. – Jeff May 17 '16 at 22:41
  • @Jeff: I edited my question.... Do you think, that it is OK? If not, how should I edit it to better match SO's guidelines? :) – unknown6656 May 18 '16 at 06:02
  • Well, as I said, I thought the question was ok as it was. Having said that, feel free to answer your own question, since it looks like you found a better solution – Jeff May 18 '16 at 09:46
  • @Jeff: I am not yet going to answer my question (I will probably in a few days), as I want to give other users the chance to answer ..... and because I have found some issues with my current solution :) – unknown6656 May 18 '16 at 16:06
  • Just a tip (doesn't sound like you're doing it anyway but still) you won't want to use a regex based parser, because generic types are not regex (nested brackets are a dead giveaway) – Jeff May 20 '16 at 08:26
  • @Jeff: Could you please elaborate this a little bit further, Sir? I do not (yet) quite understand, what you mean with `...generic types are not regex`. Do you mean, that it will be difficult to parse the type string? – unknown6656 May 20 '16 at 14:16
  • 1
    The language of generic types is not regular. Look up context sensitive and context free languages if you're interested, it's too much for me to explain here sorry – Jeff May 20 '16 at 22:20
  • @Jeff: Ah ok -- I now see what you mean, Sir. I will do some more resarch :) – unknown6656 May 20 '16 at 22:23
  • @Jeff: I added a (small) answer, in which I described how I parsed nested types --- in case you were interested ;) – unknown6656 Jun 04 '16 at 12:53
  • Thanks for sharing :-) don't forget that you can accept your own answer. – Jeff Jun 04 '16 at 21:33
  • @Jeff: I know that I can accept it, but I will not do it yet.... at least until I have found a better solution – unknown6656 Jun 04 '16 at 23:23

2 Answers2

2

I might be missing something, but it looks like you're looking for Reflection. Specifically, the GetType method. The documentation states the following about this method:

Gets the Type with the specified name, performing a case-sensitive search.

Then you will want to look at Activator.CreateInstance:

Creates an instance of the specified type using that type's default constructor

Before you create the instance for generic types, though, you'll be wanting to look at `Type.MakeGenericType':

Substitutes the elements of an array of types for the type parameters of the current generic type definition and returns a Type object representing the resulting constructed type.

You might find the answers to this other question useful too: How to use Activator to create an instance of a generic Type and casting it back to that type?

Community
  • 1
  • 1
Jeff
  • 12,555
  • 5
  • 33
  • 60
1

I solved it (temporarily) as follows:

  1. Check if the given string contains < or >
  2. If so, parse the string according to the function given below (ParseGeneric) and continue recursively using the parsed tokens
  3. If not, check for any occurrence of array brackets ([], [][], [,] etc.)
  4. Parse the type accordingly using a recursive Array::CreateInstance(Type,int[])
  5. Check for any occurrence of pointer 'stars' (*) and parse the type accordingly using Marshal::StructureToPtr


My ParseGeneric-method:
public static Type FetchGenericType(string typestring)
{
    Match m;

    if ((m = Regex.Match(typestring, )).Success)
    {
        string cls = m.Groups["class"].ToString();
        string par = m.Groups["params"].Success ? m.Groups["params"].ToString() : "";

        List<string> paramtypes = new List<string>();
        int s = 0, e = 0;

        for (int i = 0, c = 0, l = par.Length; i < l; i++)
            switch (par[i])
            {
                case ',':
                    if (c > 0)
                        goto default;

                    paramtypes.Add(par.Substring(s, e));

                    s = i + 1;
                    e = 0;
                    break;
                case '<': ++c;
                    goto default;
                case '>': --c;
                    goto default;
                default: ++e;
                    break;
            } // I know, that this is bad as hell, but what should I do instead?

        paramtypes.Add(par.Substring(s, e));

        IEnumerable<Type> @params = from type in paramtypes
                                    where !string.IsNullOrWhiteSpace(type)
                                    select FetchGenericType(type);

        string paramstring = string.Join(", ", from type in @params select "[" + type.FullName + ", " + type.Assembly.GetName().Name + "]");
        string result = @params.Count() == 0 ? cls : string.Format("{0}`{1}[{2}]", cls, @paramsCount(), paramstr);

        // The string has now the format '...List`1[[System.String, mscorlib]]'
        // or: 'System.Tuple[[System.Int32, mscorlib], [System.Object, mscorlib]]' ...
        return FetchType(result);
    }
    else
        return FetchType(typestring);
}

The function FetchType does some basic array bracket parsing and then fetches the type based on the resulted string by looking through the given assemblies.

unknown6656
  • 2,765
  • 2
  • 36
  • 52