抽象类和接口具有相同的泛型方法

4

我正在编写两个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# abstract class return derived type enumerator),但它只会引起更多问题!
是否有一种类型安全的方法可以实现此目标,而不使用对象和强制转换?
解决方案:
基于 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
}

抱歉,我也不理解这个问题。也许提供一个现实世界的例子,而不是使用“IMyInterface”和“MyBaseClass”,可以有所帮助。 - Dan Abramov
这里提供的解决方案可以解决你的问题,但我认为最终你的问题始于设计阶段,建议你退后一步重新思考设计,以避免出现这种问题。 - Mongus Pong
Search 到底搜索什么? - Jodrell
@Connell Watkins - 请查看我现在已经取消删除并编辑过的答案,其中提供了稍微不同的解决方案,也许可以展示为什么我认为通用方法并不一定是最佳选择。 - Andras Zoltan
@Jodrell - 在我的当前项目中,MyBaseClass 有一个只读的 TableName 属性和一些映射到数据库列的设置器属性,而 Search 函数创建了一个 SQL 查询来搜索 SQL 数据库。不过这在不同的项目中可能会有所不同。出于这个原因,我使用了非常通用的类名,比如 MyBaseClass,以帮助其他人解决类似的问题。 - Connell
显示剩余5条评论
5个回答

4
您可以显式实现接口的搜索方法,例如:
    public class MyDerivedClass : BasicTestApp.FirstApi.MyBaseClass, BasicTestApp.SecondApi.IMyInterface
    {
        IEnumerable<T> SecondApi.IMyInterface.Search<T>()
        {
            // do implementation
        }
    }

然而,我认为您希望在处理对象作为IMyInterface调用Search方法时调用MyBaseClass搜索方法。我无法找到一种方法,因为您有两个具有不同约束的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>();
        }

+1 这正是我要做的事情,但我试图找到一种在不固定类型的情况下完成它的方法 - 如果可能的话,保持输入类型不变。因为对于一个通用函数来说,总是期望从其封闭类型派生的类型是没有意义的。这听起来像是一个通用类。 - Andras Zoltan
我认为我喜欢它+1,但是为什么不在基类上实现接口呢? - Jodrell
@Jodrell 我认为这是因为 FirstAPI 不知道任何关于 SecondAPI 的信息。 - Andras Zoltan
我想到了一个稍微不同的解决方案,它使用了一个通用类型。看一下 - 因为我不喜欢这个(由OP的设计引起的)需要在这里关闭Search<T>参数。 - Andras Zoltan
这个方案是可行的,也是“正确”的答案。但问题在于可能有任意数量的类实现了这两个API。在我的一个项目中,我正在查看大约60个不同的类,并在所有这些类中放置相同的显式方法似乎有点过于复制代码。我担心这可能是唯一的解决方案。 - Connell

2

我先解释一下为什么这个方法对你不起作用,但我认为现在这个问题已经被理解了,所以我将省略这部分。

我已经点赞了@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>
  { }
} 

这看起来像是理想的解决方案!这也意味着我可以保持两个API的设计不变。我会尝试一下并回复你。 - Connell
我喜欢这个,它比我发布的解决方案更优雅,并且可以防止重复代码。 - IndigoDelta

1

你需要使用显式实现。

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)来组合您的约束条件或者,也许您根本不需要接口。


1
我认为你可以对接口进行显式实现,当你通过IMyInterface.Search访问方法时,编译器将运行正确的方法。

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

这是一个好主意。如果我将我的第二个API接口定义为 interface IMyInterface<T> where T : IMyInterface<T>,并且像 MyDerivedClass : MyBaseClass, IMyInterface<MyDerivedClass> 这样定义派生类,是否有一种方式绕过它呢? - Connell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接