1

I want to implement a class that behaves similarly to a Dictionary<string, dynamic>, because the dynamic keyword is causing a weird exception caused by CAS issues which I cannot resolve somewhere else on the project. Basically I want a dictionary that allows getting and setting heterogeneous values without explicit casting.

My attempt has been to get an internal Dictionary<string, Tuple<Type, object>> with the intention of casting it correctly. I would like to overload the square-bracket operator for getting and setting. However, I cannot figure out the correct syntax to do this. I would like to do something like this:

class DynDictionary<T>
{
  private Dictionary<string, Tuple<Type, object>> _dict = new Dictionary<string, Tuple<Type, object>>();

  public T this[string key]
  {
    get { return (_dict[key].Item1)_dict[key].Item2; }
    set { _dict[key] = new Tuple(typeof(value), value); }
  }
}

which does not compile for several reasons, of course. However, I can't even make this work with a generic class, and I do not want a generic class because I will then have to specify the type when instantiating, which is exactly what I'm trying to avoid.

Is there a solution to this? Am I meddling into things I shouldn't touch?

user1846231
  • 315
  • 3
  • 12

2 Answers2

2

No, there is no such option.

As you noted, for:

public T this[] { get; set; }

you need some "generic-ness" at the outer scope, as the this[] also can't be generic on its own. So, your class/whatever would be generic-<T> and force the users not only to specify the T, but also to specify only one single T for all elements.

For Dictionary<string, Tuple<Type, object>> the best you can have is:

public object this[] { get; set; }
public IMyInterface this[] { get; set; }

this is because at the time of compilation, your whole dictionary-class does not have any information about the item types. The code is limited to object as in Typle<Type,object>. The only things you can do is to return an object, or try to cast it to some other known type, like interface.

If you resign from using a this[], you can try to make a 'smart getter':

public TValue GetItem<TValue>(TKey index) { ... }
public void SetItem<TValue>(TKey index, TValue value) { ... }

which would just do all the casting (like return (TValue)tuple.Item2). However, that would have some issues like difficult usage when used in context when "TValue" is unknown. So, you'd also probably need

public object GetItem(TKey index) { ... }
public void SetItem(TKey index, object value) { ... }

just to avoid cumbersome GetItem<object>. Of course, wherever the TValue versions are used, the user would need to specify the TValue explicitely (except for the SetValue where it'd be probably inferred, not necessarily correctly).

Now, let's get back to basics. What is your idea about

public T this[] { get; set; }

anyways, hm? As you bundle together the Type and Object in the Tuple, it seems like you want to be able to pack a heterogenous items into the Dictionary, right? So, tell me, how the final user and/or how the code/compiler would be able to guess wnat is being returned:

var foo = myDictionary["bar"];

what would be the type of the foo variable? Compiler can't guess, because at compilation myDictionary only know it will hold some Tuple<Type,object> but it does not hold anything now. Also, it actually does not even exist yet, since it's being compiled.. It is simply not possible to force a 'return value' to be "typed" with the exact type taken from your Tuple.

Also, why do you even carry the Type in that Tuple? The object kept in the dictionary knows its Type, always. You don't need to carry the Type for it, you can simply .GetType() on that object with just the same effect. If you'd want to preserve upcasted type information (example: have a Bar inherited from Foo, upcast Bar as Foo and put into magiccontainer, retrieve back as Foo not Bar), then you are a bit out of luck.. Even dynamic would not help with that and would return you "dynamic object" which would know itself to be "Bar" and which would allow you to dynamically call all Bar methods. I think that carrying the Type is completely unnecessary, unless you have some strong shielding/isolation requirements - but in that case, simple "carrying a Type and casting to it" will not help at all. For such things you will probably need dynamic proxies.

Since I already got chatty, last (a bit useless, but you may still want to hear it) note: It actually is possible to force a "cast" to a type stored in a 'Type'. You can do that with expressions. You can dynamically build an Expression<>, bind it with correct Type object, and invoke.

Borrowing code from https://stackoverflow.com/a/3983342/717732 it would look like:

public static ???? castAndReturn(object item, Type type)
{
    var p = Expression.Parameter(typeof(object), "i");
    var c = Expression.Convert(p, type);
    var ex = Expression.Lambda<Func<object, ????>>(c, p).Compile();

    return ex(item);
}

but again, what return type you'd put into the ???? placeholders, hm? The only possibilities are object, dynamic, or a custom well-known common-base-type. Back to square one. D'oh. Sorry. No really, way.

Community
  • 1
  • 1
quetzalcoatl
  • 32,194
  • 8
  • 68
  • 107
  • I liked a lot your last point. I guess it should guess the same thing it would guess with a Dictionary, whatever that is. – user1846231 Oct 20 '14 at 14:40
  • @user1846231: With `Dictionary` it guesses it as .... `dynamic`. Seriously, no joking. The syntax `dynamic foo = 5; foo = new Mom(); foo = 123.0` is correct. With .Net4, we got a new magic "datatype" called `dynamic` and you can use it almost everywhere where you can use a plain datatype like `int` or `Mom`.. `Dictionary` is not magic. It simply returns its element type, and the element type is `dynamic`. – quetzalcoatl Oct 20 '14 at 14:42
  • I've also added a not about "true dynamic casting" if you'd like to play with expressions.. however while possible and interesting, it gains you nothing in this current problem.. Well, unless you'd like to heavily lambdaize/expressionize all of your code. Not really worth it IMHO. `Get("Bar")` will be much more usable, readable and easy. – quetzalcoatl Oct 20 '14 at 14:45
0

It sounds like you are trying to implement a property container exposing a generic indexer, which is (as you know) not possible in C#. However, a similar strategy can be implemented following the patterns seen in the IEditorOptions interface. In particular, note the following characteristics:

  1. The strong type associated with a key is represented by creating the EditorOptionKey<T> generic type instead of using just a string.
  2. The GetOptionValue<T> and SetOptionValue<T> methods replace your current use of an indexer property. The generic type parameter for these methods is inferred from the EditorOptionKey<T> passed as a parameter.

Code using this class might look like the following (using several fields from the DefaultOptions class):

IEditorOptions options = ...;
bool replicate = options.GetOptionValue(DefaultOptions.ReplicateNewLineCharacterOptionId);
options.SetOptionValue(DefaultOptions.IndentSizeOptionId, 2);
options.SetOptionValue(DefaultOptions.ConvertTabsToSpacesOptionId, true);
Sam Harwell
  • 97,721
  • 20
  • 209
  • 280