0

I have a weird problem. :(

I get an enumerable object from my database and want to convert the objects either to a list of base class objects or to a list of fully blown derived class objects . In some situations I want to cut of attributes I do not need - so standard polymorphism is not the way for me. This function resides in an web service and I have to optimize traffic.

Therefore I wrote a generic function for casting the whole list to the type I want. But in the case of the derived type I get an cast error. But I don't understand the message because the source object has not the type, the message is telling me.

Any ideas what is going wrong? Thx in advance!

using System;
using System.Collections.Generic;
using System.Linq;

class DBClass {
    public string id { get; set; }
    public string name { get; set; }

    public DBClass() { id = Guid.NewGuid().ToString(); }
}

class BaseClass {
    public Guid ID { get; set; }

    public static explicit operator BaseClass(DBClass x) {
        return new BaseClass() {
            ID = new Guid(x.id)
        };
    }
}

class DerivedClass : BaseClass {
    public string Name { get; set; }

    public static explicit operator DerivedClass(DBClass x) {
        return new DerivedClass() {
            ID = new Guid(x.id),
            Name = x.name
        };
    }
}

class Program
{
    static List<BaseClass> convertToList<T>(IEnumerable<DBClass> xx) where T : BaseClass
    {
        // solution from Jeroen Mostert (see comments):
        return xx.Select(obj => (BaseClass) (T) (dynamic) obj).ToList();

        // original code with error
        var l = xx.Select( obj => (T) obj);
        // T == BaseClass: everything is fine
        // T == DerivedClass: Unable to cast object of type 'BaseClass' to type 'DerivedClass'.
        // but obj is of type DBClass!

        return l.ToList() as List<BaseClass>;
    }

    static void Main(string[] args)
    {
        List < DBClass >  dbValues = new List<DBClass>();
        dbValues.Add(new DBClass());
        dbValues.Add(new DBClass());
        dbValues.Add(new DBClass());

        convertToList<BaseClass>(dbValues);     // ok
        convertToList<DerivedClass>(dbValues);  // KO
    }
}
kwrl
  • 653
  • 8
  • 12
  • Overload resolution (whether for operators or regular methods) considers only the static type of the parameters involved, not their runtime types. All the compiler has available is that `T` is at least a `BaseClass` (thanks to the constraint) so it can only select the operator converting from `DBClass` to `BaseClass` -- and then it would have to implicitly convert that to `DerivedClass`, which fails. What you want requires either `dynamic` or a virtual method. (And note that `as` never considers any user-defined operators either, though that's not the problem here.) – Jeroen Mostert Feb 06 '20 at 13:39
  • If you want a subset of the columns from your query the correct way to handle that is to select them into the desired class or an anonymous class, not to cast the entity. – juharr Feb 06 '20 at 13:41
  • To add to @Jeroen's comment, the compiler turns `(T)obj` into `(T)BaseClass.op_Explicit(obj)`, where the `(T)` is a reference conversion. That is, it inserts the call to the user-defined conversion method at compile-time (because it has to, because that's how user-defined type conversions work). So when `T` is a `DerivedClass`, it's first converting `obj` to a `BaseClass`, using its user-defined type converter, then trying to cast that to a `DerivedClass`, which fails. You get the same problem with a clearer message if you remove `where T : BaseClass` – canton7 Feb 06 '20 at 13:41
  • Depending on your scenario, you may want to consider either a micro-ORM like Dapper to handle boilerplate object conversions or a generic DTO like AutoMapper if the database classes are a given, either of which are more flexible than static conversions. Writing this stuff yourself is generally not worth it (even if you do need to "optimize" -- if the DB isn't the principal bottleneck in such scenarios you're usually doing it wrong). – Jeroen Mostert Feb 06 '20 at 13:45
  • 1
    Apropos `dynamic`: the exceedingly ugly expression `xx.Select(obj => (BaseClass) (T) (dynamic) obj).ToList()` would implement the method as desired. It's an amusing exercise to go through the individual conversions and why they're necessary. And note that this still fails, for the same reason as the static version, if you forget to define an explicit conversion operator for a derived class. In general, conversion operators are not all that useful compared to instance methods/dictionaries of delegates/what have you. – Jeroen Mostert Feb 06 '20 at 14:04
  • @JeroenMostert: That's it, thx! I'd like to vote for this solution, but unfortunately Stanley closed the topic. – kwrl Feb 06 '20 at 14:21
  • I wouldn't call that `dynamic` monstrosity a "solution", more a "curiosity" (or "amusing exercise"). While I like it, please don't do that in real code! – canton7 Feb 06 '20 at 14:36
  • @canton7: sure It's not beautiful code but it's still a pragmatic solution. I've not seen any other solution without completely changing my code structure. My original code seemed to be logically correct so I may ask (not only you but also Microsoft) if it's a C# bug or a feature? – kwrl Feb 10 '20 at 12:32
  • @kwrl Adding `dynamic` into code rarely improves it. You're adding a lot of runtime cost, in order to defer compile-time checks until runtime. This is an effect of the design of C#'s generics and user-defined operators: it's not a bug. The Shapes proposal will give you a neater way to solve this, in the future. For now, either look at the mapper suggestions above, or additionally pass a `Func` into your `converToList` method which describes how to convert your `DBClass` into your `T`. – canton7 Feb 10 '20 at 12:36

0 Answers0