0

I am trying to get two different return values from dictionary extension methods for convenience. The first being an Item of the RealType, the other being a List<RealType>.

The problem / question is this: Getting a single RealType item works like a charm, getting the List crashes and calls for IConvertable implementation. Is there no build in call to convert a full list?

Edit: I am trying to convert from Base Class to Derived Class, not the other way around. Also, I 100% know the data will be of type Derived Class and the caller is passing the correct type each time, without any chance of error.

Why is that? Can I avoid it, without "dirty" tricks? There are about two dozen RealType - classes that all extend from MyAbstractModel and get saved in a List<Dictionary<string, MyAbstractModel>. Writing code for two dozen conversions seems like no good idea, when a small dirty (?) trick seems to do it as well.

Consider the following (working) DictionaryExtention class

public static class DictionaryExtenders
{
    public static T GetItem<T>(this Dictionary<string, MyAbstractModel> instance, string key)
    {
        return (T)Convert.ChangeType(instance[key], typeof(T));
    }
}

Called like this: RealType item = myDictionary.GetItem<RealType>("choosenIDString");

Edit: Each Dictionary can and will only ever have one type present. There won't ever be two different types stored in one. Yes, this code would allow that and spew out errors, but a saveguard is not needed in this case and is not part of my question.

Now in contrast, the following GetList<RealType> extention calls for IConvertable implementation:

public static List<T> GetList<T>(this Dictionary<string, MyAbstractModel> instance)
{
    return (List<T>)Convert.ChangeType(instance.Values.ToList(), typeof(List<T>));
}

I feel like I am missing something here, because the following workaround also returns a List<RealType>, but does not call for IConvertable. I simply loop the dictionary and call GetItem<T>()each time.

public static List<T> GetList<T>(this Dictionary<string, MyAbstractModel> instance)
{
    var temp = new List<T>();
    foreach (RealType myType in instance.Values.ToList())
    {
        temp.Add(instance.GetItem<T>(myType.ID));
    }
    return temp;
}

This does not feel like a solid solution, but like a workaround. What am I missing here? Do the Lists give me a hard time due to faulty syntax on my side, or is there a reason I have to understand?

Marco Heumann
  • 121
  • 1
  • 10
  • `instance.Values.ToList()` is of type `List`. This can't be converted to `List` in general. You can change this to `return instance.Values.Select(item => (T)Convert.ChangeType(item, typeof(T))).ToList()`. – Sebastian Schumann Mar 05 '20 at 07:01
  • So basically a List can't be converted, but a single T can be? – Marco Heumann Mar 05 '20 at 07:16
  • https://stackoverflow.com/a/16967217/2729609 – Sebastian Schumann Mar 05 '20 at 07:32
  • Can you elaborate on that link? People try to convert from derived class to base class there. That is the opposite of my issue. I KNOW the list contains Lions, even tho it is called "List" so I try to convert List to List. Also: If for some reason that is not possible: WHY is it possible to convert a single Animal to a Lion, but not a herd of animals to a herd of lions? – Marco Heumann Mar 05 '20 at 07:54
  • Apology if I got your problem wrong, but it seems to me you think since Derived is a subclass of Base so must List be somehow related to List. – Tanveer Badar Mar 05 '20 at 08:47
  • Well, yes exactly. The data is stored as "base" in order to be easily reachable via a single List, but I KNOW the data that is really in there is of type "derived". That is not part of my question tho,... I just want to understand why I can easily convert single items, but not lists of the same item. I take it: there just is no call for that and I have to manually loop, but I am still not convinced that is the case. – Marco Heumann Mar 05 '20 at 14:33
  • You don't need `Convert.ChangeType` to convert individual objects since your models have an inheritance relationship. In fact if you check the [reference source](https://referencesource.microsoft.com/#mscorlib/system/convert.cs) all it does is return the original object. So you could just cast. As for "converting" the `List`, there is no inheritance relationship between your two types of lists. While they share a [type definition](https://stackoverflow.com/questions/2564745/what-is-the-difference-between-a-generic-type-and-a-generic-type-definition), they are considered unrelated. – John Wu Mar 06 '20 at 00:50

2 Answers2

0

Your GetList implementations are not valid. All values in dictionary are instances of classes derived from MyAbstractModel but you can't cast them each other in all cases. For example, ModelA and ModelB are both derived from MyAbstractModel. But you can't cast ModelA to ModelB. You can write something like this:

public static List<T> GetList<T>(this Dictionary<string, MyAbstractModel> instance) where T: MyAbstractModel
{
    return instance.Values.OfType<T>().ToList();
}

In that case you'll receive all values of particular type T.

mtkachenko
  • 5,389
  • 9
  • 38
  • 68
  • I don't ever cast them to each other. Only MyAbstractModel -> RealType. The caller knows what type the class will be and calls the method with the correct type. There won't ever be an instance of an impossible or invalid call. I will check if your version fixes the problem tho! – Marco Heumann Mar 05 '20 at 07:19
  • Nope, this is trying to do something completely different from my question. I don't need to cast to MyAbstractModel, the data is already of that type. A missing boxing conversion even stops the code from compiling with that version sadly. – Marco Heumann Mar 05 '20 at 07:25
  • @MarcoHeumann As I understand you have a set of `RealType` classes derived from `MyAbstractModel`. Here `temp.Add(instance.GetItem(myType.ID));` and here `(T)Convert.ChangeType(instance[key], typeof(T))` you cast `MyAbstractModel` to particular `RealType`. And you do it for all values in dictionary. What if some value is not `T`: it's `RealType1` not `RealType2`. That's the problem. Your `GetItem` will fail also in that case. – mtkachenko Mar 05 '20 at 07:37
  • That is simply not gonna happen. If we ever change the dataset so that is possible, we would add a saveguard obviously. – Marco Heumann Mar 05 '20 at 07:40
  • @MarcoHeumann I don't understand what do you mean. Check this https://dotnetfiddle.net/85pXdA – mtkachenko Mar 05 '20 at 07:46
  • You are intentionally adding wrong data to the dictionary. I know that is possible, but it won't ever happen for my project. It simply won't. So I don't have to spend time on adding safeguards for that case. The dictionaries will always and only have a single type of derived classes in them. Thanks for pointing the possible issue out tho! – Marco Heumann Mar 05 '20 at 08:00
  • `The dictionaries will always and only have a single type` is an important note. Code from my post should work. Why it's not compiled? – mtkachenko Mar 05 '20 at 08:05
0

you can try this way

public static List<T> GetList<T>(this Dictionary<string, yourModel> instance)
{
    return instance.Values.Select(d => (T)Convert.ChangeType(d, typeof(T))).ToList();
}
Sebastian Schumann
  • 3,204
  • 19
  • 37
Alexander
  • 46
  • 2
  • This is obviously working, but also just a slightly more fancy loop compared to my workaround. So, you are trying to say that my workaround is actually not a workaround at all and I just should go ahead with it (with prettier syntax obviously)? If so: Could you elaborate on why Lists are a hassle, while single items just work like that? – Marco Heumann Mar 05 '20 at 07:27
  • @MarcoHeumann _but also just a slightly more fancy loop compared to my workaround_ NOPE. Your _workarround_ performs two loops over the values. The first loop is in `ToList()` and the second is your explicite code. Additional to that your workarround performs a dictionary access for each item and does only work if this item contains its own key. This solution (I gave you exactly the same in comment) loops over the items only once. – Sebastian Schumann Mar 05 '20 at 07:41
  • I used the wrong words, sorry for that: I tried to make the code easily readable, not "fancy". The underlaying issue remains with this solution: I have to loop over each item and do it one by one. Does this mean that there simply is no build in call to convert a full list? (I realize it would also loop internally) So in short: The workaround idea is sound because there is no build in way to handle this? – Marco Heumann Mar 05 '20 at 07:58