简体   繁体   中英

Replacing a class implementation in C#

Can I replace (or override) a class definition with one of my own implementation?

Explanation:

  • I have a class with a System.Random instance, and I am trying to test it without modifying its original code...
  • So I am planning to replace Random class with one of my own implementation, that allows controlling generated sequence.. Please see this question .

`

class foo
{
    Random r=new Random();
    double bar()
    {
         double ans=r.NextDouble();
         .....// evaluate ans
         return ans;
    }
}

`

What are the possible ways that I can replace implementation of Random class with another one without changing (or with minimum changes) the original code file??

Edit:
One solution is to inherit Random class and modify it... but that requires changing each Random instance with the inherited one... I do not want to modify the code, because I am just testing it!!
Edit 2:
Can I say in C#: "Each Random class in this file is MyNamespace.Random not System.Random" ??

EDIT: As ChaosPandion points out, to test the logic in the method it would be better to take a double parameter ie double bar(double input) { ... } , but if you have a reason to need to inject your own source of randoms, you could use one of these approaches:

You could modify bar to take a Func<double> to obtain values from:

double bar(Func<double> source)
{
    double ans = source();
    //evaluate ans
    return ans;
}

Then you can inject your own implementation for testing, and in the main program use:

Random r = new Random();
double ans = bar(r.NextDouble);

Alternatively you could create your own IRandom interface and then implement a wrapper around the real Random class and use mocks for testing:

interface IRandom
{
    double Next();
}

public class RandomWrapper : IRandom
{
    private Random r = new Random();
    public double Next()
    {
        return this.r.NextDouble();
    }
}

You can do this and no code changes to the source are necessary.

You can use reflection to inject a mock or derived instance.

Since System.Random is not sealed , your best bet here is to derive a new class, and then you only need to override the desired methods.

I'm assuming that you are using some type of unit testing framework, so the example below is for that.

In your unit test file, do this:

class MyRandom : System.Random 
{
     public override double NextDouble()
     {     
          // return your fake random double here.
     }
}


class TestFixture 
{
     public void UnitTest()
     {
          // Create the unit under test
          foo unit = new foo();

          // use reflection to inject a mock instance
          typeof(foo)
             .GetField("r", BindingFlags.Instance | BindingFlags.NonPublic)
             .SetValue(unit, new MyRandom());

          // ACT: call method
          var result = foo.bar();

          // examine results
      }
 }

I believe that you can replace the implementation of a class like Random using tools such as JustMock or TypeMock .

However, without restructuring your original code to use dependency injection (or otherwise insert a layer of abstraction) you won't be able to do what you're asking using plain C#. That said, restructuring your code to get rid of hard dependencies makes the code cleaner and simpler, so I would say that it should be the preferred route unless you're really unable to perform the necessary restructuring.

You could derive a class from System.Random and override its methods.

class PseudoRandom : System.Random
{
    public override int Next () {...}
    public override int Next (int maxValue) {...}
    public override int Next (int minValue, int maxValue) {...}
    public override void NextBytes (byte [] buffer) {...}
    public override double NextDouble () {...}
}

Use this class to instantiate from Main.

Random random = new PseudoRandom ();

If you need to determine later on whether you are using the system class or your own derived version, you can check that using the "is" operator. (Probably not the best design, but it's quick and easy)

// We need to see which class we're working with
if (random is PseudoRandom)
{
    // Using our custom class
}
else
{
    // Using default System.Random class
}

Take a look into using Dependency Injection . It is designed for exactly this circumstance. There are several different DI/IoC frameworks for c#.

Unity and Ninject are both good but there are many others.

You need to add a layer of abstraction.

Create an interface that defines the contract you need your Random classes to support.

Create implementations of that interface, one of which should utilize the original Random class, the other would be your "random" implementation.

Provide the correct implementation when and where needed.

Answer A , a bit convoluted but it works

public class Foo 
{
   Func<double> next;
   Foo(bool use_new) 
   {
       if(use_new)
          next = (new MyRandom()).NextDouble;
       else
          next = (new Random()).NextDouble;            
   }
   void bar() 
   {
       ans = next();
       ...
   }
}

Answer B , a bit clearer on intent imho . Define a common interface which returns a function. Then just choose which function to use.

public interface IRandomProvider
{
    Func<double> NextDouble { get; }
}

public class StdRandomProvider : IRandomProvider
{
    Random r = new Random();
    Func<double> NextDouble
    {
        get { return ()=>r.NextDouble(); }
    }
}

public class MyRandomProvider : IRandomProvider
{
    MyRandom r = new MyRandom();
    Func<double> NextDouble
    {
        get { return ()=>r.MyNextDouble(); }
    }
}

public class Foo 
{
   IRandomProvider r;
   Foo(bool use_new) 
   {
       if(use_new)
          r= new MyRandomProvider();
       else
          r= new StdRandomProvider();            
   }
   void bar() 
   {
       ans = r.NextDouble();
       ...
   }
}

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