Breaking change in ConvertTo<T> in 5.5

So we noticed a breaking change in the ConvertTo logic when we attempted to upgrade to 5.5.
The issue is demonstrated in here https://gistlyn.com/?gist=d6bf646ece994da634001303fae3c738
It appears to be from change in ConvertTo in commit 3c0c728be42f872fb0550e91d9e4784ac4393814

We only saw it in the case were our web developer had inherited the DTO type in his viewmodels, and then was using ConvertTo to go back to the DTO for POSTing. The new behavior simply returns the original object back if you are trying to convert to the parent type.

As we saw no release notes on this behavior change assuming it wasn’t intended.

The ConvertTo<T> API’s purpose is to convert it into the target type, if it can be cast into the target type it will just return it as it’s more efficient.

If you wanted to clone the Type you can use:

var clone = TypeSerializer.Clone(obj);

Which is a deep copy that essentially serializes/de-serializes it. Alternatively you can use:

var to = from.CreateCopy<To>();

Which for reference type does:

var to = typeof(To).CreateInstance();
to.PopulateWith(from);

Which was the behavior you had previously, essentially a shallow copy.

FYI: Ran into this same issue. It was a little hard to track down that this ConvertTo change was the issue. No problem keeping the new v5.5 ConvertTo way. Thanks for posting the clone and deep copy code!

I feel like ConvertTo should do what its name implies. The expectation when I provide the < T> in the ConvertTo method is to get back a type of T.

var dto = model.ConvertTo<DTOTYPE>();

It does that.

That’s not true, the existing method signatures of ConvertTo<T> have not changed which returns the same T generic type as before, so you can use the exact same client code as before.

Not according to this code example here: [Code Example][1]

When I do:

child.ConvertTo<Parent>()

I expect to get a < Parent> returned, yet I get a < Child> returned

We only saw it in the case were our web developer had inherited the DTO type in his viewmodels, and then was using ConvertTo to go back to the DTO for POSTing. The new behavior simply returns the original object back if you are trying to convert to the parent type.

We saw this issue surface doing the same kind of logic as defined by @rsafier
[1]: https://gistlyn.com/?gist=7e1c96d0941072fa643ae1102c66c805

No it doesn’t, generic methods that return T always return the Type of T as specified by the method signature. If T is a subclass of the instance it will return the instance casted to T.

So it does return Parent which you can verify with the equivalent code below without using Type inference:

Parent parent = child.ConvertTo<Parent>();

Calling GetType() returns the concrete type information, not the API return type that its currently cast to.

Again you can verify this by trying to call a child method on the parent instance:

public class Child : Parent
{ 
    public void Hi() => Console.WriteLine("Hi");
}

var child = new Child();
var parent = child.ConvertTo<Parent>();

parent.Hi();

Which will fail with:

(17,8): error CS1061: ‘Parent’ does not contain a definition for ‘Hi’ and no accessible extension method ‘Hi’ accepting a first argument of type ‘Parent’ could be found (are you missing a using directive or an assembly reference?)

It’s returning the same instance because Parent is already an instance of Child, so no conversion is necessary.

The behavior you’re describing is addressed in my 2nd comment, The Convert<T> API does not convert types it doesn’t have to convert, the comment also shows different cloning APIs you can use if you’re instead looking for shallow or deep copy semantics.

Look like you’ve retracted/edited your original questions which now makes my answers sound like they’re answering a different question, and not your original one I answered.

Anyway, nothing is new, everything I’ve described has already been addressed.

Yes! I see. ConvertTo acts more like an implicit converter (not wanting to possibly lose information), while I was expecting more of a type coercion (or a cast) converter (if you lose information, you lose information).

Thank you for the clarifications and proper workaround. Perhaps we can bake this into ServiceStack to account for these edge cases?

public static T CastTo<T>(this object from)
{
    T to = from.ConvertTo<T>();
    return to.GetType() == typeof(T)
        ? to
    	: (T)typeof(T).CreateInstance().PopulateWithNonDefaultValues(from);
}

That API would be misleading since the 2nd branch is doing a shallow copy not a cast.

Ahh, I see… so to go deep you would use something more like this?

public static T CastTo<T>(this object from)
{
    T to = from.ConvertTo<T>();
    return to.GetType() == typeof(T)
        ? to
    	: from.ToSafeJson().FromJson<T>();
}

That’s even worse, it’s now doing a deep copy.

I’m not sure what API would best describe that behavior, it’s basically SelfOrDeepCopy<T>().

What you’re looking for is probably CreateCopy<T>:

i.e. it always returns a copy (never the same instance).

Ahh I C… so it’s because the ConvertTo when converting a subtype to a supertype, uses the same reference in memory, which is the subtype, and thus there is no way to coerce it into its supertype without creating a new instance as the supertype.

That’s what casting and what ConvertTo<T> does when it’s a subtype of T, it returns the same instance casted to the target T Type. But you’re still getting the original instance returned, not a different instance which would then be a shallow copy not a cast.

I’m sorry I have to open this thread again, but I ran into a similar problem now.
In my case, both classes (Child and Parent) have a route attribute, because I use them as some kind of internal proxy.
Yes, I know about the ProxyFeature, but I cannot use it here, because I have to do some special stuff before proxying the request.

After doing a

var parent = child.ConvertTo<Parent>();

and then

parent.ToGetUrl();

it still returns the Route of the Child class.
I think this is, because ToGetUrl() calls ToUrl internally, which then uses GetType() to determine the request type - which still returns Child.

Just wanted to mention this.
I will change my code now to use CreateCopy() instead, so I have a “real” instance of Parent.