C# Generics – How to Avoid Redundant Methods

cgenerics

Let's assume I have two classes that look like this (the first block of code and the general problem are related to C#):

class A 
{
    public int IntProperty { get; set; }
}

class B 
{
    public int IntProperty { get; set; }
}

These classes cannot be changed in any way (they are part of a 3rd party assembly). Therefore, I cannot make them implement the same interface, or inherit the same class that would then contain IntProperty.

I want to apply some logic on the IntProperty property of both classes, and in C++ I could use a template class to do that quite easily:

template <class T>
class LogicToBeApplied
{
    public:
        void T CreateElement();

};

template <class T>
T LogicToBeApplied<T>::CreateElement()
{
    T retVal;
    retVal.IntProperty = 50;
    return retVal;
}

And then I could do something like this:

LogicToBeApplied<ClassA> classALogic;
LogicToBeApplied<ClassB> classBLogic;
ClassA classAElement = classALogic.CreateElement();
ClassB classBElement = classBLogic.CreateElement();   

That way I could create a single generic factory class that would work for both ClassA and ClassB.

However, in C#, I have to write two classes with two different where clauses even though the code for the logic is exactly the same:

public class LogicAToBeApplied<T> where T : ClassA, new()
{
    public T CreateElement()
    {
        T retVal = new T();
        retVal.IntProperty = 50;
        return retVal;
    }
}

public class LogicBToBeApplied<T> where T : ClassB, new()
{
    public T CreateElement()
    {
        T retVal = new T();
        retVal.IntProperty = 50;
        return retVal;
    }
}

I know that if I want to have different classes in the where clause, they need to be related, i.e. to inherit the same class, if I want to apply the same code to them in the sense that I described above. It is just that it is very annoying to have two completely identical methods. I also do not want to use reflection because of the performance issues.

Can somebody suggest some approach where this can be written in a more elegant fashion?

Best Answer

Add a proxy interface (sometimes called an adapter, occasionally with subtle differences), implement LogicToBeApplied in terms of the proxy, then add a way to construct an instance of this proxy from two lambdas: one for the property get and one for the set.

interface IProxy
{
    int Property { get; set; }
}
class LambdaProxy : IProxy
{
    private Function<int> getFunction;
    private Action<int> setFunction;
    int Property
    {
        get { return getFunction(); }
        set { setFunction(value); }
    }
    public LambdaProxy(Function<int> getter, Action<int> setter)
    {
        getFunction = getter;
        setFunction = setter;
    }
}

Now, whenever you need to pass in an IProxy but have an instance of the third party classes, you can just pass in some lambdas:

A a = new A();
B b = new B();
IProxy proxyA = new LambdaProxy(() => a.Property, (val) => a.Property = val);
IProxy proxyB = new LambdaProxy(() => b.Property, (val) => b.Property = val);
proxyA.Property = 12; // mutates the proxied `a` as well

Additionally, you can write simple helpers to construct LamdaProxy instances from instances of A or B. They can even be extension methods to give you a "fluent" style:

public static class ProxyExtension
{
    public static IProxy Proxied(this A a)
    {
      return new LambdaProxy(() => a.Property, (val) => a.Property = val);
    }

    public static IProxy Proxied(this B b)
    {
      return new LambdaProxy(() => b.Property, (val) => b.Property = val);
    }
}

And now construction of proxies looks like this:

IProxy proxyA = new A().Proxied();
IProxy proxyB = new B().Proxied();

As for your factory, I'd see if you can refactor it into a "main" factory method that accepts an IProxy and performs all logic on it and other methods that just pass in new A().Proxied() or new B().Proxied():

public class LogicToBeApplied
{
    public A CreateA() {
      A a = new A();
      InitializeProxy(a.Proxied());
      return a; // or maybe return the proxy if you'd rather use that
    }

    public B CreateB() {
      B b = new B();
      InitializeProxy(b.Proxied());
      return b;
    }

    private void InitializeProxy(IProxy proxy)
    {
        proxy.IntProperty = 50;
    }
}

There's no way to do the equivalent of your C++ code in C# because C++ templates rely on structural typing. As long as two classes have the same method name and signature, in C++ you can call that method generically on both of them. C# has nominal typing - the name of a class or interface is part of its type. Therefore, the classes A and B cannot be treated the same in any capacity unless an explicit "is a" relationship is defined through either inheritance or interface implementation.

If the boilerplate of implementing these methods per class is too much, you can write a function that takes an object and reflectively builds a LambdaProxy by looking for a specific property name:

public class ReflectiveProxier 
{
    public object proxyReflectively(object proxied)
    {
        PropertyInfo prop = proxied.GetType().GetProperty("Property");
        return new LambdaProxy(
            () => prop.GetValue(proxied),
            (val) => prop.SetValue(proxied, val));
     }
}

This fails abysmally when given objects of incorrect type; reflection inherently introduces the possibility of failures the C# type system cannot prevent. Luckily you can avoid reflection until the maintenance burden of the helpers becomes too great because you're not required to modify the IProxy interface or the LambdaProxy implementation to add the reflective sugar.

Part of the reason this works is that LambdaProxy is "maximally generic"; it can adapt any value that implements the "spirit" of the IProxy contract because the implementation of LambdaProxy is completely defined by the given getter and setter functions. It even works if the classes have different names for the property, or different types that are sensibly and safely representable as ints, or if there's some way to map the concept that Property is supposed to represent to any other features of the class. The indirection provided by the functions gives you maximal flexibility.