简体   繁体   中英

Cast generic dictionary in C#

Is there a way to cast a dictionary ia one-liner and without all the overhead in C# .net 3?

var result = new Dictionary<string, AccessoryVariant>();
foreach (BaseVariant variant in mVariants.Values)
{
    result.Add(variant.Id, (AccessoryVariant)variant);
}
return result;

I would like to do something like this:

return (Dictionary<string, AccessoryVariant>)mVariants;

/Sven

Assuming you're using .NET 3.5 or 4, you can use the ToDictionary method in LINQ:

return mVariants.Values.ToDictionary(v => v.Id, v => (AccessoryVariant) v);

Alternatively:

return mVariants.Values.Cast<AccessoryVariant>().ToDictionary(v => v.Id);

Or (assuming mVariants is already using the Id as the key):

return mVariants.ToDictionary(pair => pair.Key,
                              pair => (AccessoryVariant) pair.Value);

Note that you can't cast the dictionary directly, because assuming mVariant is a Dictionary<string, BaseVariant> , someone could add a non-AccessoryVariant to the dictionary as a value, which would clearly mess up any code which (reasonably) assumed that the Dictionary<string, AccessoryVariant> only contained AccessoryVariant values.

I've made a fast method to transform the values of entries in a dictionary by creating a new dictionary and bypassing the hash calculation (given that the key stays the same). It's done in IL so it's very fast.

See http://drake7707.blogspot.com/2011/03/fast-value-operation-on-dictionary-in-c.html for full method details and more explanation.

Usage is:

 pCopyDic = personsDic.CastValues<Person, PersonCopy>(p => (PersonCopy)p);

Full method: (you'll have to edit the caching of the delegate though, I didn't include it because it's pretty trivial and different in which context you use it)

  /// <summary>
  /// Transforms each value in a dictionary to a new object with the given cast delegate
  /// </summary>
  public static Dictionary<string, To> CastValues<From, To>(this Dictionary<string, From> fromDic, Func<From, To> cast)
  {
   MetaData data;
   if (!store.TryGetValue(typeof(From), out data))
   {
    data = new MetaData();
    store.Add(typeof(From), data);
   }


   if (data.CastDictionaryValues == null)
   {
    // Create ILGenerator
    DynamicMethod dymMethod = new DynamicMethod("DoDictionaryCastValues", typeof(Dictionary<string, To>), new Type[] { typeof(Dictionary<string, From>) }, true);
    ConstructorInfo newDictionaryTo = typeof(Dictionary<string, To>).GetConstructor(new Type[] { typeof(int) });
    FieldInfo fldEntries = typeof(Dictionary<string, From>).GetField("entries", BindingFlags.NonPublic | BindingFlags.Instance);

    FieldInfo fldEntryKey = fldEntries.FieldType.GetElementType().GetField("key", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
    FieldInfo fldEntryValue = fldEntries.FieldType.GetElementType().GetField("value", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

    ILGenerator generator = dymMethod.GetILGenerator();

    // define labels for loop
    Label loopConditionCheck = generator.DefineLabel();
    Label insideLoop = generator.DefineLabel();

    // define local variables
    generator.DeclareLocal(typeof(Dictionary<string, To>)); // toDic , 0
    generator.DeclareLocal(fldEntries.FieldType.GetElementType()); // pair entry<string, from>, 1
    generator.DeclareLocal(typeof(int)); // i, 2
    generator.DeclareLocal(typeof(int)); // count, 3;
    generator.DeclareLocal(typeof(Dictionary<string, To>).GetField("entries", BindingFlags.NonPublic | BindingFlags.Instance).FieldType.GetElementType()); // entry<to> 4;

    // store count
    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Callvirt, typeof(Dictionary<string, From>).GetProperty("Count").GetGetMethod());
    generator.Emit(OpCodes.Stloc_S, 3);

    generator.Emit(OpCodes.Ldloc_S, 3); // load count and pass it as capacity parameter for toDic
    generator.Emit(OpCodes.Newobj, newDictionaryTo); // toDic = new ...
    generator.Emit(OpCodes.Stloc_0);

    // COPY Dictionary fields to toDic

    // toDic.buckets = fromDic.buckets;
    generator.Emit(OpCodes.Ldloc_0);
    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Ldfld, typeof(Dictionary<string, From>).GetField("buckets", BindingFlags.NonPublic | BindingFlags.Instance));
    generator.Emit(OpCodes.Stfld, typeof(Dictionary<string, To>).GetField("buckets", BindingFlags.NonPublic | BindingFlags.Instance));

    // toDic.comparer = fromDic.comparer;
    generator.Emit(OpCodes.Ldloc_0);
    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Ldfld, typeof(Dictionary<string, From>).GetField("comparer", BindingFlags.NonPublic | BindingFlags.Instance));
    generator.Emit(OpCodes.Stfld, typeof(Dictionary<string, To>).GetField("comparer", BindingFlags.NonPublic | BindingFlags.Instance));

    // toDic.count = fromDic.count;
    generator.Emit(OpCodes.Ldloc_0);
    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Ldfld, typeof(Dictionary<string, From>).GetField("count", BindingFlags.NonPublic | BindingFlags.Instance));
    generator.Emit(OpCodes.Stfld, typeof(Dictionary<string, To>).GetField("count", BindingFlags.NonPublic | BindingFlags.Instance));

    // toDic.freeCount = fromDic.freeCount;
    generator.Emit(OpCodes.Ldloc_0);
    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Ldfld, typeof(Dictionary<string, From>).GetField("freeCount", BindingFlags.NonPublic | BindingFlags.Instance));
    generator.Emit(OpCodes.Stfld, typeof(Dictionary<string, To>).GetField("freeCount", BindingFlags.NonPublic | BindingFlags.Instance));

    // toDic.freeList = fromDic.freeList;
    generator.Emit(OpCodes.Ldloc_0);
    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Ldfld, typeof(Dictionary<string, From>).GetField("freeList", BindingFlags.NonPublic | BindingFlags.Instance));
    generator.Emit(OpCodes.Stfld, typeof(Dictionary<string, To>).GetField("freeList", BindingFlags.NonPublic | BindingFlags.Instance));

    // toDic.entries = new Entry<,>[fromDic.entries.Length];
    generator.Emit(OpCodes.Ldloc_0);
    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Ldfld, fldEntries);
    generator.Emit(OpCodes.Ldlen);
    generator.Emit(OpCodes.Newarr, typeof(Dictionary<string, To>).GetField("entries", BindingFlags.NonPublic | BindingFlags.Instance).FieldType.GetElementType());
    generator.Emit(OpCodes.Stfld, typeof(Dictionary<string, To>).GetField("entries", BindingFlags.NonPublic | BindingFlags.Instance));

    // End COPY

    // i = 0;
    generator.Emit(OpCodes.Ldc_I4_0);
    generator.Emit(OpCodes.Stloc_2);

    generator.Emit(OpCodes.Br, loopConditionCheck); // perform loop test
    generator.MarkLabel(insideLoop);
    {
     // pair = fromDic.entries[i];
     generator.Emit(OpCodes.Ldarg_0); // load fromDic on stack
     generator.Emit(OpCodes.Ldfld, fldEntries); // load entries field from dic on stack
     generator.Emit(OpCodes.Ldloc_2); // load i
     generator.Emit(OpCodes.Ldelem, fldEntries.FieldType.GetElementType()); // load fromDic.entries[i]
     generator.Emit(OpCodes.Stloc_1);

     // bypass add & insert manually into entries from toDic

     // entryTo = new Entry<,>();
     generator.Emit(OpCodes.Ldloca_S, 4);
     generator.Emit(OpCodes.Initobj, typeof(Dictionary<string, To>).GetField("entries", BindingFlags.NonPublic | BindingFlags.Instance).FieldType.GetElementType());

     // entryTo.key = entryFrom.key
     generator.Emit(OpCodes.Ldloca_S, 4);
     generator.Emit(OpCodes.Ldloc_1);
     generator.Emit(OpCodes.Ldfld, fldEntries.FieldType.GetElementType().GetField("key", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));
     generator.Emit(OpCodes.Stfld, typeof(Dictionary<string, To>).GetField("entries", BindingFlags.NonPublic | BindingFlags.Instance).FieldType.GetElementType().GetField("key", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));

     // entryTo.hashCode = entryFrom.hashCode
     generator.Emit(OpCodes.Ldloca_S, 4);
     generator.Emit(OpCodes.Ldloc_1);
     generator.Emit(OpCodes.Ldfld, fldEntries.FieldType.GetElementType().GetField("hashCode", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));
     generator.Emit(OpCodes.Stfld, typeof(Dictionary<string, To>).GetField("entries", BindingFlags.NonPublic | BindingFlags.Instance).FieldType.GetElementType().GetField("hashCode", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));

     // entryTo.next = entryFrom.next
     generator.Emit(OpCodes.Ldloca_S, 4);
     generator.Emit(OpCodes.Ldloc_1);
     generator.Emit(OpCodes.Ldfld, fldEntries.FieldType.GetElementType().GetField("next", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));
     generator.Emit(OpCodes.Stfld, typeof(Dictionary<string, To>).GetField("entries", BindingFlags.NonPublic | BindingFlags.Instance).FieldType.GetElementType().GetField("next", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));

     // entryTo.value = 'to'
     generator.Emit(OpCodes.Ldloca_S, 4);
     // call cast(pair.value)
     generator.Emit(OpCodes.Ldloc_1);
     generator.Emit(OpCodes.Ldfld, fldEntryValue); // load value from pair on stack
     generator.Emit(OpCodes.Call, cast.Method);
     // and store the to value into the new entry
     generator.Emit(OpCodes.Stfld, typeof(Dictionary<string, To>).GetField("entries", BindingFlags.NonPublic | BindingFlags.Instance).FieldType.GetElementType().GetField("value", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));

     // toDic.entries[i] = entryTo;
     generator.Emit(OpCodes.Ldloc_0); // load entries[]
     generator.Emit(OpCodes.Ldfld, typeof(Dictionary<string, To>).GetField("entries", BindingFlags.NonPublic | BindingFlags.Instance));
     generator.Emit(OpCodes.Ldloc_2); // load i
     generator.Emit(OpCodes.Ldloc_S, 4);
     generator.Emit(OpCodes.Stelem, typeof(Dictionary<string, To>).GetField("entries", BindingFlags.NonPublic | BindingFlags.Instance).FieldType.GetElementType()); // save element

     generator.Emit(OpCodes.Ldc_I4_1); // load 1
     generator.Emit(OpCodes.Ldloc_2); // load i
     generator.Emit(OpCodes.Add); // i + 1
     generator.Emit(OpCodes.Stloc_2); // i = i+1
    }
    generator.MarkLabel(loopConditionCheck);

    generator.Emit(OpCodes.Ldloc_2); // load i
    generator.Emit(OpCodes.Ldloc_S, 3); // load count
    generator.Emit(OpCodes.Blt, insideLoop); // i < fromDic.entries.Length

    generator.Emit(OpCodes.Ldloc_0); // return toDic;
    generator.Emit(OpCodes.Ret);

    data.CastDictionaryValues = dymMethod.CreateDelegate(typeof(Func<Dictionary<string, From>, Dictionary<string, To>>));
   }
   return ((Func<Dictionary<string, From>, Dictionary<string, To>>)data.CastDictionaryValues)(fromDic);
  }

There is no way to cast to a dictionary of standard type since you can't add an explicit operator to it. But you can cast to a dictionary of your own type like this:

class Program
{
    static void Main(string[] args)
    {
        var list = new List<Entity<string>>()
        {
            new Entity<string>("1", "Some data 1"),
            new Entity<string>("2", "Some data 2")
        };

        var myCollection = (MyDictionary<string, Entity<string>>)list;
    }
}

public class Entity<T> : IId<T>
{
    private readonly T id;
    private string data;

    public Entity(T id, string data)
    {
        this.id = id;
        this.data = data;
    }

    public T Id
    {
        get { return id; }
    }

    public string Data
    {
        get { return data; }
        set { data = value; }
    }
}

public interface IId<T>
{
    T Id { get; }
}

public class MyDictionary<TKey, TValue>
    where TValue : IId<TKey>
{
    private readonly Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();

    public void Add(TKey key, TValue value)
    {
        dictionary.Add(key, value);
    }

    public bool Remove(TKey key)
    {
        var wasActuallyRemoved = dictionary.Remove(key);

        return wasActuallyRemoved;
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        var wasSuccessfull = dictionary.TryGetValue(key, out value);

        return wasSuccessfull;
    }

    public static explicit operator MyDictionary<TKey, TValue>(List<TValue> items)
    {
        var myDictionary = new MyDictionary<TKey, TValue>();

        foreach (var item in items)
        {
            myDictionary.Add(item.Id, item);
        }

        return myDictionary;
    }
}

This is the answer to your question, though the approach with LINQ usage is better.

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