简体   繁体   中英

call a generic method with an object of an unknown type

I have this method

public string DictionaryToString<T, U>(Dictionary<T, U> dict)
{
  var valueStrings = dict.Select(x => x.Key.ToString() + ": " + x.Value.ToString());
  return String.Join("\n", valueStrings);
}

And I have this object that I want to pass into it

if ((value !=null) && value.GetType().IsGenericType &&
  value.GetType().GetGenericTypeDefinition() == typeof (Dictionary<,>))
{
   var castValue = value as Dictionary<,>; // this cast does not work
   return DictionaryToString(castValue);
}
else
{
  return value.ToString();
}

I can use reflection code like this in .Net 4.5

var targetMethodInfo = typeof(MyType).GetMethod("DictionaryToString");
var valueTypeArgs = value.GetType().GenericTypeArguments;
var genericMethod = targetMethodInfo.MakeGenericMethod(valueTypeArgs);
var result = genericMethod.Invoke(this, new[] {value });
return result.ToString();

But Type.GenericTypeArguments is new in .Net 4.5. So how can I do that cast in .Net 4.0?

You are only calling ToString on the key and value, so simply have this method take an IDictionary (non-generic), you are not using anything in there that is type-specific to T or U .

You can then simply cast all arguments to IDictionary :

var d = arg as IDictionary;

if (d != null)
{
    var res = DictionaryToString(d);
}

You may also need to amend the DictionaryToString implementation:

static string DictionaryToString(IDictionary d)
{
    var vals = new List<string>();

    foreach (DictionaryEntry de in d)
    {
        vals.Add(de.Key.ToString() + ": " + de.Value.ToString());
    }

    return String.Join("\n", vals);
}

Alternatively, if you really want to use LINQ, you could try casting to dynamic (it isn't possible to cast to anything else as this could be a generic dictionary ( KeyValuePair<> ) or non-generic hashtable ( DictionaryEntry )):

var valueStrings = d.Cast<dynamic>().Select(de => de.Key.ToString() + ": " + de.Value.ToString());
return string.Join("\n", valueStrings);

This basically "duck types" the existence of the Key and Value properties.

This sounds like it might be a case for the old System.Collections namespace:

    private static string DictionaryToString(IDictionary dict) {
        if (null == dict) throw new ArgumentNullException("dict");

        var valueStrings = new List<string>();
        foreach (DictionaryEntry item in dict) {
            valueStrings.Add(item.Key + ": " + item.Value);
        }
        return string.Join("\n", valueStrings.ToArray());
    } 

    private static string Test(object value) {

        var dict = value as IDictionary;
        if (dict != null) {
            return DictionaryToString(dict);
        }

        if (value == null) {
            return null;
        }

        return value.ToString();
    }

    private static void Main(string[] args) {

        var aDictionary = new Dictionary<int, string> {
            { 1, "one" },
            { 2, "two" },
            { 3, "three" }
        };

        Console.WriteLine(Test(aDictionary));

        var anotherDictionary = new Dictionary<string, object> {
            { "one", 1 },
            { "two", "2" },
            { "three", new object() }
        };

        Console.WriteLine(Test(anotherDictionary));

        Console.ReadLine();
    }

Reasoning:

The non-generic IDictionary will be a collection of key-value pairs in which the key is an object and the value is an object . All instances of object support ToString , so all keys and values of the collection can be converted to a string without knowing their specific types.

The reason why this does not work:

var castValue = value as Dictionary<,>

is because the generic type Dictionary<TKey, TValue> requires 2 type arguments. Without those type arguments, the collection is not generic. You'd be better off using the non-generic IDictionary if you do not know the key or value types at compile-time.

GetGenericTypeDefinition is available for previous versions http://msdn.microsoft.com/en-us/library/system.type.getgenerictypedefinition(v=vs.100).aspx . I think the 4.5 page on MSDN is missing the "Other versions" dropdown.

Why not?

if ((value !=null) && value.GetType().IsGenericType &&
       value.GetType().GetGenericTypeDefinition() == typeof (Dictionary<,>))
{
   return DictionaryToString(castValue);
}
else
{
  return value.ToString();
}

You could cast to dynamic in .NET 4.0. The prior typeof(Dictionary<,>) check will ensure that you won't get runtime errors.

var castValue = value as dynamic;
return DictionaryToString(castValue);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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