使用相同的泛型方法抽象类和接口

本文关键字:抽象类 接口 泛型方法 | 更新日期: 2023-09-27 17:57:32

我正在编写两个API,它们将用于我的许多项目。有些项目我使用其中一个API,有些使用另一个,但我的大多数项目都会同时使用这两个API。我试着把它们设计成完全独立的,但有一点我很纠结。

namespace FirstApi {
    public abstract class MyBaseClass {
        //constructor, some methods and properties
        public IEnumerable<T> Search<T>() where T : MyBaseClass, new() {
            //search logic here. must use generics as I create new instances of T here
        }
    }
}

namespace SecondApi {
    public interface IMyInterface {
        //some property and method signatures
        IEnumerable<T> Search<T>() where T : IMyInterface, new();
    }
}
namespace MyProject {
    public class MyDerivedClass : MyBaseClass, IMyInterface {
    }
}

两个API都需要此搜索方法。第二个API在其他调用IMyInterface.Search<T>()的类中有一些功能,我希望继承MyBaseClass的类使用MyBaseClass中定义的Search<T>函数。

编译错误:方法"MyBaseClass.Search()"的类型参数"T"的约束必须与接口方法"IMyInterface.Search"的类型形参"T"约束匹配。请考虑改用显式接口实现

注意:调用Search时,T将始终是继承的抽象类或接口的派生类。这是我在C#2.0(C#抽象类返回派生类型枚举器)中找到的实现这一点的唯一方法,它只会导致更多问题!

有没有一种类型安全的方法可以实现这一点,而不使用对象和铸造?

解决方案:

根据Andras Zoltan接受的答案,我在我的项目中创建了这个类,并且必须为每个使用这两个API的项目重新创建这个类。

public abstract class ApiAdapter<TAdapter> : MyBaseClass, IMyInterface where TAdapter: MyBaseClass, IJsonObject, new()
{
    IEnumerable<T> IJsonObject.Search<T>()
    {
        foreach (TAdapter row in base.Search<TAdapter>())
            yield return (T)(IMyInterface)row;
    }
}

然后我像这样继承这个类。

public class Client : ApiAdapter<Client> {
    //everything else can go here
}

使用相同的泛型方法抽象类和接口

您可以显式地实现接口Search方法,例如

    public class MyDerivedClass : BasicTestApp.FirstApi.MyBaseClass, BasicTestApp.SecondApi.IMyInterface
    {
        IEnumerable<T> SecondApi.IMyInterface.Search<T>()
        {
            // do implementation
        }
    }

但是,我认为您要求在将对象处理为IMyInterface的代码部分调用Search<T>方法时调用MyBaseClassSearch方法。我看不出有什么办法,因为您有两个具有不同约束的T类型,它们无法关联。如果您在Search方法的两个定义中都执行了where T : BasicTestApp.FirstApi.MyBaseClass, IMyInterface, new();,那么您不会有问题,但这会将两个API连接在一起

以下是显式实现的接口方法的可能实现。它并没有避开演员阵容,但至少保持了整洁。

        IEnumerable<T> SecondApi.IMyInterface.Search<T>()
        {
            var results = base.Search<MyDerivedClass>();
            return results.Cast<T>();
        }

我在回答开始时阐述了为什么它对您不起作用,但我认为现在已经很清楚了,所以我将省略它。

我对@IndigoDelta的答案投了赞成票,但它突出了我不喜欢这里整体设计的地方——我偷偷怀疑你实际上应该使用通用接口和通用类;不是通用方法,因为它没有任何意义:

注意:当调用Search时,T将始终是继承的抽象类或接口的派生类。

我正在把这个解决方案加入其中;我认为这更好,因为这意味着每个派生类型都不需要重新实现IMyInterface.Search方法,而且它在一定程度上实际执行了您提到的这个规则。它是一个通用类型,专门将两个API连接在一起,这意味着派生类型不需要做任何事情:

namespace MyProject
{
  using FirstApi;
  using SecondApi;
  public class SecondAPIAdapter<T2> : MyBaseClass, IMyInterface
    where T2 : SecondAPIAdapter<T2>, new()
  {
    #region IMyInterface Members
    IEnumerable<T> IMyInterface.Search<T>()
    {
      return Search<T2>().Cast<T>();
    }
    #endregion
  }
  //now you simply derive from the APIAdapter class - passing
  //in your derived type as the generic parameter.
  public class MyDerivedClass : SecondAPIAdapter<MyDerivedClass>
  { }
} 

我认为你可以显式地实现接口,以及何时通过IMyInterface访问方法。搜索-编译器将运行正确的方法。

您需要使用显式实现。

public class MyDerivedClass : MyBaseClass, IMyInterface
{
    // The base class implementation of Search inherited
    IEnumerable<T> IMyInterface.Search<T>()
    {
        // The interface implementation
        throw new NotImplementedException();
        // this would not work because base does not implement IMyInterface 
        return base.Search<T>();
    }     
}

由于实现方式不同,所以这是有意义的。如果它们没有什么不同,那么要么基类应该实现接口,你应该使用协方差(仅限.Net 4.0)来组合你的约束,要么你根本不需要接口。

我希望我没有感到困惑,你能不能不要改变你的定义,这样:

public interface IMyInterface<in T>
{
    //some property and method signatures
    IEnumerable<U> Search<U>() where U : T, new();
}

提供T的通用参数,可用于强制实现为T:类型提供搜索函数约束

public abstract class MyBaseClass : IMyInterface<MyBaseClass>
{
    public virtual IEnumerable<T> Search<T>() where T : MyBaseClass, new()
    {
    }
}

这样,您的派生类型就是:

public class MyDerivedClass : MyBaseClass
{
}

然后你可以搜索为:

var derived = new MyDerivedClass();
IMyInterface<MyDerivedClass> iface = impl;
var results = iface.Search<MyDerivedClass>();