2
\$\begingroup\$

I need to transform an entity from an external DLL to another entity which is from a different external DLL.

My question is: Is this the best solution or is there a better solution (type-save)? I hope there is a better idee because I don't really like to use the dynamic type.

using ExternalDllOne.Fruit; //contains(Apple, Banana, Melon, Peach)
using ExternalDllTwo.Fruit; //contains(ReturnApple, ReturnBanana, ReturnMelon, ReturnPeach)

public static dynamic Transform(Type type, string path)
{
    switch (type.Name)
    {
        case nameof(Banana):
            {
                Banana serializedFruit = SerializerFactory<Banana>.Create(SerializerType.Xml).Deserialize(path);
                ReturnBanana fruit = TransformBanana(serializedFruit);
                return fruit;
            }
        case nameof(Apple):
            {
                Apple serializedFruit = SerializerFactory<Apple>.Create(SerializerType.Xml).Deserialize(path);
                ReturnApple fruit =  TransformApple(serializedFruit);
                return fruit;
            }
        case nameof(Peach):
            {
                Peach serializedFruit = SerializerFactory<Peach>.Create(SerializerType.Xml).Deserialize(path);
                ReturnPeach fruit =  TransformPeach(serializedFruit);
                return fruit;
            }
        case nameof(Melon):
            {
                Melon serializedFruit = SerializerFactory<Melon>.Create(SerializerType.Xml).Deserialize(path);
                ReturnMelon fruit =  TransformMelon(serializedFruit);
                return fruit;
            }
        default:
            throw new ArgumentException($@"Type: [{type.Name}] is not a valid type for this application");
    }
}

The conversion is done thru a facade which is located in an external DLL. They way the Transform<fruit>() works is for every fruit type the same:

private static ReturnBanana TransformBanana(Banana fruit)
{
    var extraSettings = GetBananaSettings();

    var transformedFruit = FruitTransformationFacade.Transform(fruit, extraSettings);

    return transformedFruit;
}

Using the transformer method I have this code:

private static dynamic Load(string path)
{
    XDocument document = FileHelper.ParseFile(path);

    Type fileType = TypeHelper.DetermineFileType(document);

    dynamic component = Transformer.Transform(fileType, path);

    return component;
}

Do I need a wrapper object for each Return<fruit> entity with a coherent Interface, and return the interface?

\$\endgroup\$
  • 1
    \$\begingroup\$ Could add more information about the entities and the conversion process? \$\endgroup\$ – t3chb0t May 5 '17 at 9:44
  • \$\begingroup\$ The entities are all different and have nothing in common with eachother except that they are serializable. \$\endgroup\$ – GuacaMollee May 5 '17 at 9:57
  • \$\begingroup\$ Do you have only SerializerFactory<T> or also a non-generic version where T is a Type parameter? \$\endgroup\$ – Adriano Repetti May 5 '17 at 10:00
  • \$\begingroup\$ I only have the generic version \$\endgroup\$ – GuacaMollee May 5 '17 at 10:41
3
\$\begingroup\$

I didn't really like the other answer, basically it says you must return object instead, and wants more POCO's (one for each object) that handles everything. There's a more dynamic (pun-intended) way to avoid dynamic.

Let's think about this from a more abstract standpoint: we want to take any object and transform it to any other object assuming we have a transform method for it.

I made an assembly, Console application, and started playing with it and created a namespace First and a Second, threw some Fruit types together, and it looks like so:

namespace CSharp_Test1.First
{
    public class Apple
    {
        public string Color { get; } = "Red";
    }
}

namespace CSharp_Test1.First
{
    public class Banana
    {
        public string Color { get; } = "Yellowish";

        public int Count { get; set; } = -5;
    }
}

namespace CSharp_Test1.Second
{
    public class OtherApple
    {
        public string Color { get; } = "Other Red";
    }
}

namespace CSharp_Test1.Second
{
    public class OtherBanana
    {
        public string Color { get; } = "Other Yellowish";

        public int Count { get; set; } = 5;
    }
}

I gave Banana and OtherBanana a Count only to prove a point, because my serialization mock is pretty lame and I didn't want to build actual serialization as it doesn't matter:

public class Serializer<T>
    where T : new()
{
    public T Deserialize(string path) =>
        new T();
}

public static class SerializerFactory<T>
    where T : new()
{
    public static Serializer<T> Create(SerializerType type) =>
        new Serializer<T>();
}

public enum SerializerType
{
    Xml
}

Now you can call SerializerFactory<T>.Create(SerializerType).Deserialize(string) and get a T from it, but we're not going to do that.

Now how do we wrap a generic SerializerFactory<T>.Create(SerializerType).Deserialize(string) construct? With a NonGenericSerializer:

public class NonGenericSerializer
{
    private object _rootSerializer;

    public object Deserialize(string path) =>
        _rootSerializer
            .GetType()
            .GetMethod("Deserialize")
            .Invoke(_rootSerializer, new object[] { path });

    public static NonGenericSerializer Create(Type type, SerializerType serializerType)
    {
        var ngs = new NonGenericSerializer();
        var factory = typeof(SerializerFactory<>).MakeGenericType(new Type[] { type });
        ngs._rootSerializer = factory.GetMethod("Create").Invoke(factory, new object[] { serializerType });
        return ngs;
    }
}

We use a boatload of reflection, but it's pretty simple. We use object instead of dynamic, and we have to call NonGenericSerializer.Create(Type, SerializerType).Deserialize(string), but it works.

At this point I want to talk about the transformations: we have a method for each object type as you mentioned:

private static OtherApple Transform(Apple apple) =>
    new OtherApple();

private static OtherBanana Transform(Banana banana) =>
    new OtherBanana() { Count = banana.Count };

Pretty simple, I didn't pull all your code as I didn't need it, I just needed a mock.

Within our transformation system we need to find a method dynamically, sorta. We want to find the method that has the return type of our input parameter. Of course I should mention that the final API call will look like this:

OtherApple otherApple = TransformUtilities.Transform<OtherApple>("some path");

Right, back to TransformUtilities, we need a Transform<T>(string path) method:

public static T Transform<T>(string path)
{
    var transformMethod = GetTransformMethod<T>();
    return Transform<T>(NonGenericSerializer.Create(
        transformMethod.GetParameters().First().ParameterType,
        SerializerType.Xml
    ).Deserialize(path));
}

That makes sense, we want to find the method we're transforming with, then deserialize it (non-generically), then provide that value to the transform method.

private static T Transform<T>(object item) =>
    (T)typeof(TransformUtilities)
        .GetMethod("Transform",
                   BindingFlags.NonPublic | BindingFlags.Static,
                   null,
                   new Type[] { item.GetType() },
                   null)
        .Invoke(null, new object[] { item });

So we invoke the method in the Transform<T>(object) method.

private static MethodInfo GetTransformMethod<T>()
{
    var typeFullName = typeof(T).FullName;
    var candidateMethods =
        typeof(TransformUtilities)
            .GetMethods(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.ExactBinding)
            .Where(x => x.Name == "Transform");

    if (!candidateMethods.Any(x => x.ReturnType.FullName == typeFullName))
    {
        throw new ArgumentException($@"Type: [{typeFullName}] is not a valid type for this application");
    }

    return candidateMethods
        .First(x => x.ReturnType.FullName == typeFullName);
}

And finally we find the appropriate method.

All this boils down to the following: now when you want to transform a Peach to an OtherPeach, you simple create a public static OtherPeach Transform(Peach obj) method.

My main looks like the following:

Console.WriteLine(TransformUtilities.Transform<OtherApple>("some path").Color);
Console.WriteLine(TransformUtilities.Transform<OtherBanana>("some path").Color);
Console.WriteLine(TransformUtilities.Transform<OtherBanana>("some path").Count);

Pretty freaking simple. If we have a Transform method, then we can do the transformation, if not then we have that exception.

As a test, consider the following method in Transform:

private static string Transform(int value) => (value + 150).ToString();

And call it:

Console.WriteLine(TransformUtilities.Transform<string>("some path"));

Output is 150. Remove the Transform method and the exception is thrown:

System.ArgumentException: 'Type: [System.String] is not a valid type for this application'

Is this a lot of boilerplate? Not really but we could go with 'Sure' I guess. Does it make adding/removing transformations stupid simple? Damn right. You could also create a Transformations class which has the private static Other____ Transform(____ obj) methods, and modify the GetTransformMethod<T>() and T Transform<T>(object) method as follows:

private static MethodInfo GetTransformMethod<T>()
{
    var typeFullName = typeof(T).FullName;
    var candidateMethods =
        typeof(Transformations)
            .GetMethods(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.ExactBinding)
            .Where(x => x.Name == "Transform");

    if (!candidateMethods.Any(x => x.ReturnType.FullName == typeFullName))
    {
        throw new ArgumentException($@"Type: [{typeFullName}] is not a valid type for this application");
    }

    return candidateMethods
        .First(x => x.ReturnType.FullName == typeFullName);
}

private static T Transform<T>(object item) =>
    (T)typeof(Transformations)
        .GetMethod("Transform",
                    BindingFlags.NonPublic | BindingFlags.Static,
                    null,
                    new Type[] { item.GetType() },
                    null)
        .Invoke(null, new object[] { item });

(Basically, the typeof(Transformations) should be the class name where the Transform methods are.)

This changes your Load to:

private static T Load<T>(string path)
{
    XDocument document = FileHelper.ParseFile(path);

    T component = Transformer.Transform(path);

    return component;
}

Of course you could also remove the type-safety from Load and eliminate dynamic by returning an object, but then you have to type-case and such.

Since you mention (in comments) that you have no idea about the return type at all, I've come up with a modification that works for both scenarios.

We had the GetTransformMethod<T>() which would take type T and find the method that had the best return, well now we have a GetTransformedMethod<T>() which does the opposite: given type T find the best method to transform it.

private static MethodInfo GetTransformedMethod<T>()
{
    var typeFullName = typeof(T).FullName;
    var candidateMethods =
        typeof(TransformUtilities)
            .GetMethods(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.ExactBinding)
            .Where(x => x.Name == "Transform");

    if (!candidateMethods.Any(x => x.GetParameters().First().ParameterType.FullName == typeFullName))
    {
        throw new ArgumentException($@"Type: [{typeFullName}] is not a valid type for this application");
    }

    return candidateMethods
        .First(x => x.GetParameters().First().ParameterType.FullName == typeFullName);
}

Then we create a simple Transform(Type, object) method that will find the Transform method that fits and call it, then return the result as an object. You can then pass that directly to a serializer which should be able to serialize it appropriate.

public static object Transform(Type type, string path)
{
    var result = NonGenericSerializer.Create(
        type,
        SerializerType.Xml
    ).Deserialize(path);

    var transformLocateMethod =
        typeof(TransformUtilities)
            .GetMethod("GetTransformedMethod", BindingFlags.NonPublic | BindingFlags.Static);

    var transformMethod = (MethodInfo)transformLocateMethod.MakeGenericMethod(new Type[] { result.GetType() }).Invoke(null, null);

    return transformMethod.Invoke(null, new object[] { result });
}

And our new test calls:

Console.WriteLine(((OtherApple)TransformUtilities.Transform(typeof(Apple), "some path")).Color);
Console.WriteLine(((OtherBanana)TransformUtilities.Transform(typeof(Banana), "some path")).Color);
Console.WriteLine(((OtherBanana)TransformUtilities.Transform(typeof(Banana), "some path")).Count);

I suggest you evaluate your code, and determine why such a seemingly simple task has been blown out of proportion. Usually you need to know a type for loading/saving behavior to work properly, so I'm curious where you call Load that doesn't care about what actual type it is.

\$\endgroup\$
  • \$\begingroup\$ Well, if all you have is a Type object then generics won't help you and since OP is using this as a parameter one can assume that he does not have the T, otherwise I guess he would already have made it generic because apparently he knows how it works. So yes, you are right, my solution is an ugly workaround for dynamic. \$\endgroup\$ – t3chb0t May 5 '17 at 19:18
  • \$\begingroup\$ @t3chb0t I did go under the assumption that the OP knows the return type, if not then a lot of the type coercion from this could be removed, and simply return object from the public static T Transform<T>(string path), remove the <T> constraint, and you're good to go. \$\endgroup\$ – 410_Gone May 5 '17 at 19:21
  • \$\begingroup\$ @t3chb0t is right. I only know the input type, Not the return type. This application should read a xml file. Determine the xml type and transform this type to another type and deserialize again. So i cant use generics on Load(). \$\endgroup\$ – GuacaMollee May 9 '17 at 8:42
  • \$\begingroup\$ @PolkaHard Added more to the answer that should do what you want. \$\endgroup\$ – 410_Gone May 9 '17 at 17:57
  • \$\begingroup\$ @EBrown I will give it a go, I will let you know the result. Thanks to both of your help so far! \$\endgroup\$ – GuacaMollee May 10 '17 at 10:59
3
\$\begingroup\$
public static dynamic Transform(Type type, string path)

If I should return an object of any type I'd rather use the object type for it then dynamic. I don't know how you actually use it so I assume that all objects have some set of common properties but don't share a any common interface.

In that case I think it would make more sense to create an artifical wrapper for those objects so that you can read those properties (or methods) without having to use dynamic everywhere.

public class Fruit // = ExternalDllTwoEntityWrapper
{ 
    private readonly dynamic _value;
    Fruit(object obj) => _value = obj;
    public string Name => (string)_value.Name;
}

Whether you use dynamic or reflection internally to access those properties is another question.

If you however can change one of those external dlls then an abstraction would be the obvious way to solve this:

public interface IFruit
{
    string Name { get; }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.