简体   繁体   English

在C#中使用泛型-从泛型类调用泛型类

[英]Using Generics in C# - Calling Generic class from a Generic class

I have a class similar to the following: 我有一个类似于以下课程:

public abstract class Manager<T, TInterface> : IManager<T> where TInterface : IRepository<T>
{
    protected abstract TInterface Repository { get; }

    public virtual List<T> GetAll()
    {
        return Repository.GetAll();
    }
}

This works perfectly fine, however, is there a way to get away from having the TInterface in the abstract class declaration and in the resulting class that extends my generic abstract class: 这样做非常好,但是,有一种方法可以避免在抽象类声明和扩展我的通用抽象类的结果类中使用TInterface:

public class TestManager : Manager<TestObject, ITestRepository>, ITestManager

I am forced to use ITestRepository and make the Repository property abstract due to the fact that it can contain custom methods that I need to know about and be able to call. 我被迫使用ITestRepository并使Repository属性变得抽象,这是因为它可能包含我需要了解并能够调用的自定义方法。

As I continue to build layers, I will have to keep doing this process the whole way up the stack. 当我继续构建层时,我将不得不一直在整个堆栈中执行此过程。 Examples would be if I had a generic abstract controller or service layer: 例如,如果我有一个通用的抽象控制器或服务层:

public class TestService : Service<TestObject, ITestManager>, ITestService

Is there a better way to do this or is this the best practice to allow a generic class to call another generic class? 有没有更好的方法可以做到这一点,或者这是允许通用类调用另一个通用类的最佳实践?

It seems that all you want to do is to make Manager<T> testable, and use a mock as a repository that you can query for special members. 似乎您要做的只是使Manager<T>可测试,并使用模拟作为可查询特殊成员的存储库。

If that's the case, maybe you can change your design to this: 如果是这样,也许您可​​以将设计更改为:

public class Manager<T> : IManager<T> {
  protected IRepository<T> Repository { get; set; }
  // ...
  public virtual List<T> GetAll() {
    return Repository.GetAll();
  }
}

Now, all the specifics of testing are in a testing subclass: 现在,所有测试的细节都在一个测试子类中:

public class TestingManager<T> : Manager<T> {
  public new ITestRepository<T> Repository {
    get {
      return (ITestRepository<T>)base.Repository;
    }
    set {
      base.Repository = value;
    }
  }
}

When you write your unit tests, you create TestingManager<T> instances (referenced through TestingManager<T> declared variables and fields), and you provide them with a test repository. 编写单元测试时,将创建TestingManager<T>实例(通过TestingManager<T>声明的变量和字段进行引用),并为它们提供测试存储库。 Whenever you query their Repository , you'll always get a strongly-typed test repository. 每当您查询他们的Repository ,总会得到一个强类型的测试存储库。

UPDATE: 更新:

There's another way to solve this, without a subclass. 没有子类,还有另一种解决此问题的方法。 You declare your repository objects as test repositories that you pass to Manager<T> s and you query them directly, without going through the Manager<T> . 您将存储库对象声明为传递给Manager<T>的测试存储库,然后直接查询它们,而无需通过Manager<T>

[Test]
public void GetAll_Should_Call_GetAll_On_Repository_Test() {
  var testRepository = new TestRepository();
  var orderManager = new Manager<Order>(testRepository);
  // test an orderManager method
  orderManager.GetAll();
  // use testRepository to verify (sense) that the orderManager method worked
  Assert.IsTrue(testRepository.GetAllCalled);
}

No, you can't get around it. 不,你无法解决它。 You can try, but the result will be ugly and in some way incorrect. 您可以尝试,但结果会很丑陋,并且在某种程度上不正确。 The reason is that you are asking generics not to be generic but still be generic. 原因是您要求泛型不泛型,但仍然是泛型。

If a new class uses a generic class, either in inheritance or composition, and it itself does not know enough to specify the type parameters to the generic class it is using, then it must itself be generic. 如果新类使用继承或组合形式的泛型类,并且它本身对所使用的泛型类的类型参数的了解不多,则它本身必须是泛型的。 It is analogous the method call chains, where a method may pass parameters along to another method. 类似于方法调用链,其中方法可以将参数传递给另一个方法。 It can't make up the arguments to the inner method, but must rather take them as parameters itself from a caller that does know what they are. 它不能构成inner方法的参数,而必须从确实知道它们是什么的调用方将它们本身当作参数。 Type parameters are the same. 类型参数相同。

One thing that does make this feel like code smell is the fact that you can't have a variable of type Manager<,>. 有一件事确实让这感觉就像代码味道的是,你不能有类型管理器<,>的变量的事实。 It has to be fully type-specified. 它必须是完全类型指定的。 One solution I've come up with is to have non-generic interfaces that the generic classes implement. 我想出的一种解决方案是拥有泛型类实现的非泛型接口。 These interfaces have as much of the public interface of the generic class as is possible (they can't have methods or properties that reference the type parameters). 这些接口尽可能具有泛型类的公共接口(它们不能具有引用类型参数的方法或属性)。 Then you can pass around variables of the type of the interface and not have to specify type parameters. 然后,您可以传递接口类型的变量,而不必指定类型参数。

Example: 例:

interface IExample {
    string Name { get; }
    void SomeNonGenericMethod(int i);
}

class Example<T> : IExample {
    public string Name { get { ... } }

    public void SomeNonGenericMethod(int i) {
        ...
    }

    public T SomeGenericMethod() {
        ...
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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