2

I want to use C# 8 default interface implementation to face a performance issue in my code.

Actually, I have this intefaces :

public interface IDataAdapter {}
public interface IDataAdapter<T> : IDataAdapter
{
   void Insert(T value);
}

I actually have to do reflection across all IDataAdapter, check generic type and call Insert by reflection for a specific T instance. What I wish to do is :

public interface IDataAdapter 
{
   void InsertValue(object value);
}
public interface IDataAdapter<T> : IDataAdapter
{
   void Insert(T value); 
   public void InsertValue(object value) => Insert(value as T);
}

The compiler says to use the keyword new to mask the inherited method. However, the only thing I'm trying to accomplish is to have a non-generic method already implemented to make all IDataAdapter<T> implementations to only have to implement the generic version.

Is this something I can accomplish or it's still impossible ? I already know that using an abstract class is a way to solve this issue, but I want to allow a developper to have a class that implements many IDataAdapter...

This is my current reflection code :

public IEnumerable<IDataAdapter> DataAdapters { get; }

    public Repository(IEnumerable<IDataAdapter> dataAdapters)
    {
        DataAdapters = dataAdapters;
    }

    public async Task SaveAsync()
    {
        foreach (var item in aggregates)
        {
            foreach (var dataAdapter in DataAdapters)
            {
                if (dataAdapter.GetType().GetInterfaces().Any(i => i.IsGenericType && i.GetGenericArguments()[0] == item.GetType()))
                {
                    dataAdapter.GetType().GetMethod("Insert", new[] { item.GetType() }).Invoke(dataAdapter, new[] { item });
                }
            }

        }
    }
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
cdie
  • 4,014
  • 4
  • 34
  • 57
  • Is an extension method not an option? – Damien_The_Unbeliever Aug 22 '19 at 09:54
  • I don't really see the way go through with an extension method. I'll update my question to show the reflection I'm doing actually. – cdie Aug 22 '19 at 09:55
  • "I want to allow a developer to have a class that implements many IDataAdapter". If a developer does that and then calls the non-generic InsertValue, what type of T would the non-generic choose? – nvoigt Aug 22 '19 at 10:00
  • *"but I want to allow a developper to have a class that implements many IDataAdapter…"* If a class implements `IDataAdapter` and `IDataAdapter`, which of the two methods will be called by `InsertValue`? As you can see, that's impossible. – Ivan Stoev Aug 22 '19 at 10:01
  • InsertValue is completely "T agnostic", because it's stored as document behind, this is why object is a valid option for non generic one – cdie Aug 22 '19 at 10:02

2 Answers2

1

I recognize this problem where you need to look up the IDataAdapter implementation which knows how to handle a certain type of item. I've done something similar for a "view plugin" system, where I would look for the view plugin that knows how to render a certain type. This is useful if you can't know in advance what type of objects you'll need to render.

As far as I know, trying to shoehorn more compile-time type safety into this pattern won't really work, or if it does then it won't actually provide any benefits. I would just declare IDataAdapter like this:

public interface IDataAdapter 
{
   void InsertValue(object value);
   Type SupportedType { get; }
}

If a data adapter supports multiple types, you can make it IEnumerable<Type> SupportedTypes instead, or maybe replace the property by a bool SupportsType(Type) method.

Wim Coenen
  • 66,094
  • 13
  • 157
  • 251
  • This is as viable alternative indeed, but it won't give the developper the fancy code editing C# allow with generics. They'll have to cast value to have it, and if it's a viable alternative, this is one I prefer no to go to, because other piece of code looks for retrieving DataApter by their generic type parameters... – cdie Aug 22 '19 at 10:17
  • Thinking about your own preferred solution again, I'm not sure which problem you are solving by having an `InsertValue(object value);` method. If you need to write code anyway that uses reflection to select the correct IDataAdapter implementation, then that code can just directly call `Insert(T value)`, no? – Wim Coenen Aug 22 '19 at 10:34
  • Actually what I was doing, but I was trying to improve performance for a bunch of adapters – cdie Aug 22 '19 at 10:42
  • OK, but I'm saying that if you are worried about the performance, then adding a `InsertValue(object value)` method will not change much. You still need to find the correct IAdapter instance with reflection, which is likely the slow part because it's a loop executed over and over again. The only small difference is that the method call itself could be done without reflection. To really improve performance, you need to quickly find the right IAdapter, e.g. by having a `Dictionary` mapping item types to IAdapter instances. – Wim Coenen Aug 22 '19 at 14:44
1

From an object oriented point of view, what you are trying to do can't be done.

Suppose you create the following class hierarchy:

public interface  IFoo{}
public interface  IBar{}
public class A: IFoo{}
public class B: IFoo{}
public class C:IFoo,IBar {}

And then the following adapters:

public class TestA : IDataAdapter<A>{}
public class TestB : IDataAdapter<B>{}
public class TestC : IDataAdapter<C>{}
public class TestIFoo : IDataAdapter<IFoo>{}
public class TestIBar : IDataAdapter<IBar>{}
public class TestIBoth : IDataAdapter<IFoo>,IDataAdapter<IBar>{}

What should happen if TestA receive an instance of A is quite easy. But what about TestIFoo receive a C? Currently your reflection code won't work because you test type equality (does C equals IFoo? No! Even if C as IFoo is ok). This breaks Liskov substitution principle. If something works with a class then it should also work with any of its subclasses.

Let's suppose you fix above point. Now what about TestIBoth receiving a C? Is there two different implementation of Insert in it? Of course, this is required by inheritence! But then... do you have to insert C twice? Or do you have to insert it just once in the first fitting method?

The reason why you have to go through reflection is because all those questions needs an algorithmic answer. Your compiler won't be able to answer (which makes the language prevent it by the way)

In the end I would strongly recommend to use a very different solution (like the one proposed by Wim Coenen)

Bruno Belmondo
  • 2,299
  • 8
  • 18
  • As a side note, I should have specified that the reflection code is POC, not production ready code. But indeed, you're right, even if I remove interfaces from your answer (meaning `C : A, B`), this would break. My only solution there is abstract classes for each T. But great explaination though – cdie Aug 22 '19 at 10:43