简体   繁体   中英

C# - Type Parameters in Constructor - No Generics

I have a class that I am trying to do unit tests on. The class is a WCF Service Class. (Making it a generics class is not my goal.)

I have a data access layer (DAL) type (called UserDAL) that is instantiated in many methods. To get these methods under test, I need to get this local variables mocked. (Each instance of UserDAL has method specific value in it, so changing it a class level variable would result in messy code, so I would rather not do that.)

What I am thinking would be nice is to overload the constructor and pass in a type to use in the local methods. The empty param constructor would still create a normal UserDAL, but the overloaded one would have a mock type that implements IUserDAL.

I am not sure of the syntax to say I want to pass in a type. Note that I am not trying to pass in a variable, but a type.

Example:

public class MyWCFClass: IMyWCFClass
{
    private TypeParam _myUserDALType;
    public MyWCFClass()
    {
        _myUserDALType = UserDAL;
    }
    public MyWCFClass(TypeParam myUserDALType)
    {
        _myUserDALType = myUserDALType;
    }

    //methods to use it
    public MyMethod()
    {
        IUserDAL userDAL = new _myUserDALType();
        //Call method in IUserDAL
        userDAL.CreateUser();
    }


    // Several similar methods that all need a different UserDAL go here
    .....
}

So, I don't know what kind of type TypeParam is (I made that up) or if this kind of think is even possible.

If you have a non generics solution that would be great.

What you are really looking for is Dependency Injection, but you can do this by passing in a Type argument and then using Activator.CreateInstance(Type) to create the object when you need it.

As far as doing real DI (which will make doing this testing a lot easier), I know that Spring.Net works reasonable well.

Use a Type, and use Activator.CreateInstance to instantiate it:

private Type _myUserDALType;

IUserDAL userDAL = Activator.CreateInstance(_myUserDALType) as IUserDAL;

You mean Type , using Activator.CreateInstance to create instances:

public class MyWCFClass: IMyWCFClass
{
    private Type _myUserDALType;
    public MyWCFClass()
    {
        _myUserDALType = typeof(UserDAL);
    }
    public MyWCFClass(Type myUserDALType)
    {
        _myUserDALType = myUserDALType;
    }

    //methods to use it
    public void MyMethod()
    {
        IUserDAL userDAL = (IUserDAL) Activator.CreateInstance(_myUserDALType );
        //Call method in IUserDAL
        userDAL.CreateUser();
    }
}

Your real problem is not in the generics or lack thereof. Your real problem is that MyWFCClass is calling both new and the method. As per Misko Hevery , you get the best testability by separating classes that call new from classes that implement logic. Instead of having MyWFCClass somehow know the type that you want to implement and using reflection, just pass the IUserDal object to the constructor, allowing the test harness to pass in a mock object when needed.

If, for some reason, you can't do this and you can't use generics, then you have to do it yourself. Pass a Type object to the MyWFCClass constructor, then use reflection to find and invoke the constructor you want.

If you want to pass in a type, you can use the Type object:

public class A
{
  public A(Type classType)
  {
    object myObject = Activator.CreateInstance(...classType...);
  }
}

public class B
{
...
}

public class C
{
  public static void main(string[] args)
  {
    A a = new A(typeof(B));
  }

}

If IUserDAL defines the interface that your WCF service needs to get its job done, why not just take an instance of it as a constructor parameter? And since WCF requires a default constructor, why not have that default constructor call your parameterized constructor with a default implementation?

public class MyWCFClass : IMyWCFClass
{
    private readonly IUserDAL _userDAL;

    public MyWCFClass()
        : this(new DefaultUserDAL())
    {
    }

    public MyWCFClass(IUserDAL userDAL)
    {
        _userDAL = userDAL;
    }
}

If you're using a dependency injection container, you could expose it as a singleton and satisfy the parameterized constructor by using that singleton:

public MyWCFClass()
    this(Container.Instance.Resolve<IUserDAL>())
{
}

With this approach, your WCF class has everything it needs to get its job done, but it is still unit-testable. Moreover, it is not responsible for creating its dependencies, which is a good thing.

Far simpler, and more consistent with other applications that have this problem, would be to extract an interface on UserDal, then you would have something more like:

public MyWCFClass() : this(new UserDAL())
{
}
public MyWCFClass(IUserDal userDAL)
{
    _myUserDAL = myUserDAL;
}

This is also easier to use with dependency-injection frameworks than your proposed method, though that's certainly a secondary concern

(Edited to clarify an alternative solution based on other comments)

If your DAL is essentially worthless after use because it is mutated, take a constructor with IUserDalFactory instead, with one method Create() .

In C# there is a type called "Type". With it you can create a parameter and pass in any valid type.

private void MyMethod(Type myType)
{
  //Do something
}

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