首页 > 解决方案 > 仅当在 C# 中实现时才在接口实现上调用泛型方法

问题描述

给定一个通用的逆变接口:

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

这有一个约束Param,一个标记接口:

public interface Param { }

存在具体实现的地方:

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");
}

Param 实现只是演示了这种情况:

public class Concrete1 : Param { }

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

问题

是否可以构建参数列表并测试该类是否实现特定接口并调用它,最好不进行反射?

是否有可能在Non-reflective way using ref - call in a loop下面工作?

TheClass c = new TheClass();

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

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

当然上面不会编译。

反光解决方案

我们可以看看 [反射] 来做到这一点......

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});
}

控制台输出:

Called Concrete1
Called Concrete2

使用 ref 的非反射方式

这不太管用。

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);
    }
}

循环调用

以下循环将 Param 作为类型传递,导致没有控制台输出:

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

直接打电话

但是,这会输出到控制台:

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

输出:

Called Concrete1
Called Concrete2

标签: c#genericsmethods

解决方案


不,如果没有反射或其他骇人听闻的方法,这是不可能的。但是您可以使用以下方法检查所有类型:

            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);
            }

但是,我不建议这样做,因为当添加新的“具体”类时,必须一次又一次地手动扩展此代码。这很容易被遗忘。


推荐阅读