简体   繁体   中英

Call generic method on interface implementation only if implemented in C#

Given a generic, contravariant interface:

public interface DoThis<in TParam> where TParam : Param
{
    void Do(TParam param);
}

That has a constraint around Param , a marker interface:

public interface Param { }

Where a concrete implementation exists:

public class TheClass : DoThis<Concrete1>, DoThis<Concrete2>
{
    public void Do(Concrete1 param) => Console.WriteLine("Called Concrete1");
    public void Do(Concrete2 param) => Console.WriteLine("Called Concrete2");
}

Where the Param implementations just demonstrate this scenario:

public class Concrete1 : Param { }

public class Concrete2 : Param { }
  
public class Concrete3 : Param { }

Question

Is it possible to build a list of parameters and test whether the class implements the particular interface and call it, preferably without reflection?

Is it possible to get the Non-reflective way using ref - call in a loop to work below?

TheClass c = new TheClass();

Param[] @params = {new Concrete1(), new Concrete2(), new Concrete3()};

foreach (var p in @params)
{
    c.Do(p);
}

Of course the above won't compile.

Reflective Solution

We can look at [reflection] to do this...

foreach (var p in @params)
{
    MethodInfo? method = c.GetType()
        .GetMethods()
        .FirstOrDefault(
            x => x.Name == nameof(DoThis<Param>.Do)
            && x.GetParameters().Length == 1
            && x.GetParameters()[0].ParameterType == p.GetType());
                
    method?.Invoke(c, new object[] {p});
}

Console Output:

Called Concrete1
Called Concrete2

Non-reflective way using ref

This does not quite work.

private void DoParam<TParam>(TheClass c, TParam @event) 
    where TParam : Param
{
    DoParam(ref c, ref @event);
}

private void DoParam<TParam>(ref TheClass c, ref TParam @event)
    where TParam : Param
{
    if (c is DoThis<TParam> projects)
    {
        projects.Do(@event);
    }
}

Call in a loop

The following loop passes Param as the type resulting in no console output:

foreach (var p in @params)
{
    DoParam(c, p);
}

Call directly

However, this does output to the console:

DoParam(c, new Concrete1());
DoParam(c, new Concrete2());
DoParam(c, new Concrete3());

Output:

Called Concrete1
Called Concrete2

No this is not possible without reflection or other hacky approaches. But you can check all types using the following approach:

            TheClass c = new TheClass();

            Param[] @params = { new Concrete1(), new Concrete2(), new Concrete3() };

            foreach (var p in @params)
            {
                if (p is Concrete1 c1 && c is DoThis<Concrete1> t1) t1.Do(c1);
                else if (p is Concrete2 c2 && c is DoThis<Concrete2> t2) t2.Do(c2);
                else if (p is Concrete3 c3 && c is DoThis<Concrete3> t3) t3.Do(c3);
            }

I would not recommend this, however, because this code has to be manually expanded again and again when new "Concrete" classes are added. This can easily be forgotten.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM