Click here to Skip to main content
15,900,110 members
Articles / Programming Languages / C#

Extending Anything!!!

Rate me:
Please Sign up or sign in to vote.
2.33/5 (2 votes)
19 Sep 2015CPOL2 min read 5.1K   2
Extending anything

In today’s section, we’ll see how to extend even an inaccessible class and write tests against that. But, before that, let us assume that you have the following class:

C#
internal class internalClass:baseClass
    {
      public string getFirstValue()
      {
          return "extension";
      }
      internal class secondClass
      {
          internal string getSecondValue()
          {
              return "smith";
          }
          private class thirdClass
          {
              private string getThirdValue()
              {
                  return "dave";
              } 
          }
      }
    }

Here, it’s quite easy to write a test for getFirstValue() as shown below in the snippet:

C#
[TestMethod]
       public void Class1Test()
       {
           var obj1 = new internalClass();
           Assert.AreEqual("extension",obj1.getFirstValue());
       }

And, this will pass. Similarly, we can write a test for the next one as well and that will also pass.

C#
[TestMethod]
        public void Class2Test()
        {
            var obj2 = new internalClass.secondClass();
            Assert.AreEqual("smith", obj2.getSecondValue());
        }

But, for the third one, which is private, we need to make use of reflection as shown below:

C#
[TestMethod]
      public void Class3Test()
      {
          var type = typeof (internalClass.secondClass).GetNestedType
                            ("thirdClass", BindingFlags.NonPublic);
          var methodInfo = type.GetMethod("getThirdValue",
                           BindingFlags.NonPublic | BindingFlags.Instance);
          var obj3 = Activator.CreateInstance(type);
          Assert.AreEqual("dave",methodInfo.Invoke(obj3,null));
      }

Here, using reflection, I got access of that private method and then created an instance of that and hence tested the same. However, let us suppose that you would like to extend this class and make it more reusable. In real time project scenario, your extension could be more meaningful. But, here for demo purposes, I am just extending the class to modify the output.

C#
internal  static class internalClassExtensions
 {
    public static string getFirstValueUpper(this internalClass objClass)
    {
        return objClass.getFirstValue().ToUpper();
    }
 }

In order to test the same, I can go ahead and write the test case as shown below:

C#
[TestMethod]
      public void Class1ExtensionTest()
      {
          var obj1 = new internalClass();
          Assert.AreEqual("EXTENSION", obj1.getFirstValueUpper());
      }

Very powerful and easy to use, right!!!

Similarly, we can go ahead and extend the second class as shown below:

C#
public static string getSecondValueUpper(this internalClass.secondClass objSecondClass)
      {
          return objSecondClass.getSecondValue().ToUpper();
      }

And, again test for the same will look like:

C#
[TestMethod]
  public void Class2ExtensionTest()
  {
      var obj2 = new internalClass.secondClass();
      Assert.AreEqual("SMITH", obj2.getSecondValueUpper());
  }

However, if we try to access the third class as shown below, it won’t be accessible, since it’s private.

C#
//Cannot access private class as shown below
  /*public static string getThirdValueUpper(this internalClass.secondClass.thirdClass )
  {

  }*/

Here, to rescue us again, reflection will come into the picture. Below is the snippet for the same.

C#
//hence, if we really need to extend inaccessible class, we can extend object
     public static string getThirdValueUpper(this object obj)
     {
         var upper = string.Empty;
         var type = typeof (internalClass.secondClass).GetNestedType
                           ("thirdClass", BindingFlags.NonPublic);
         if (obj.GetType() == type)
         {
             //Now, I gain the access of private method as well
             var method = type.GetMethod("getThirdValue",
                                          BindingFlags.NonPublic | BindingFlags.Instance);
             var result = method.Invoke(obj, null) as string;
             upper = result.ToUpper();
         }
         return upper;
     }

Similarly, we can write a test for the same as shown below:

C#
[TestMethod]
    public void Class3ExtensionTest()
    {
        var type = typeof(internalClass.secondClass).GetNestedType
                         ("thirdClass", BindingFlags.NonPublic);
        var methodInfo = type.GetMethod("getThirdValue",
                                         BindingFlags.NonPublic | BindingFlags.Instance);
        var obj3 = Activator.CreateInstance(type);
        Assert.AreEqual("DAVE", obj3.getThirdValueUpper());
    }

Now, let us go ahead and add one abstract class on top of this and extend the same. Below is the snippet in finished form.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ExtensionMethods.Classes
{
    internal abstract class baseClass
    {
        public virtual string getFirstName()
        {
            return "rahul";
        }

        protected virtual string getSecondName()
        {
            return "sahay";
        }
    }
  internal class internalClass:baseClass
    {
      public string getFirstValue()
      {
          return "extension";
      }
      internal class secondClass:baseClass
      {
          public override string getFirstName()
          {
              return "John";
          }

          internal string getSecondValue()
          {
              return "smith";
          }
          private class thirdClass
          {
              private string getThirdValue()
              {
                  return "dave";
              } 
          }
      }
    }
}

And, its extension will look like:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace ExtensionMethods.Classes
{
   internal  static class internalClassExtensions
    {
       public static string getBaseStringUpper(this baseClass objClass)
       {
           return objClass.getFirstName().ToUpper();
       }
       public static string getFirstValueUpper(this internalClass objClass)
       {
           return objClass.getFirstValue().ToUpper();
       }

       public static string getSecondValueUpper(this internalClass.secondClass objSecondClass)
       {
           return objSecondClass.getSecondValue().ToUpper();
       }

       //Cannot access private class as shown below
       /*public static string getThirdValueUpper(this internalClass.secondClass.thirdClass )
       {
           
       }*/

       //hence, if we really need to extend inaccessible class, we can extend object
       public static string getThirdValueUpper(this object obj)
       {
           var upper = string.Empty;
           var type = typeof (internalClass.secondClass).GetNestedType
                             ("thirdClass", BindingFlags.NonPublic);
           if (obj.GetType() == type)
           {
               //Now, I gain the access of private method as well
               var method = type.GetMethod("getThirdValue", 
                            BindingFlags.NonPublic | BindingFlags.Instance);
               var result = method.Invoke(obj, null) as string;
               upper = result.ToUpper();
           }
           return upper;
       }
    }
}

And, its finished tests will look like:

C#
using System;
using System.Reflection;
using ExtensionMethods.Classes;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Legacy.Tests.Extension_Tests
{
    [TestClass]
    public class InternalClassTests
    {
        [TestMethod]
        public void Class1Test()
        {
            var obj1 = new internalClass();
            Assert.AreEqual("extension",obj1.getFirstValue());
        }
        [TestMethod]
        public void Class2Test()
        {
            var obj2 = new internalClass.secondClass();
            Assert.AreEqual("smith", obj2.getSecondValue());
        }
        [TestMethod]
        public void Class3Test()
        {
            var type = typeof (internalClass.secondClass).GetNestedType
                              ("thirdClass", BindingFlags.NonPublic);
            var methodInfo = type.GetMethod("getThirdValue", 
                             BindingFlags.NonPublic | BindingFlags.Instance);
            var obj3 = Activator.CreateInstance(type);
            Assert.AreEqual("dave",methodInfo.Invoke(obj3,null));
        }
        [TestMethod]
        public void Class1ExtensionTest()
        {
            var obj1 = new internalClass();
            Assert.AreEqual("EXTENSION", obj1.getFirstValueUpper());
        }
        [TestMethod]
        public void Class2ExtensionTest()
        {
            var obj2 = new internalClass.secondClass();
            Assert.AreEqual("SMITH", obj2.getSecondValueUpper());
        }
        [TestMethod]
        public void Class3ExtensionTest()
        {
            var type = typeof(internalClass.secondClass).GetNestedType
                             ("thirdClass", BindingFlags.NonPublic);
            var methodInfo = type.GetMethod("getThirdValue", 
                                   BindingFlags.NonPublic | BindingFlags.Instance);
            var obj3 = Activator.CreateInstance(type);
            Assert.AreEqual("DAVE", obj3.getThirdValueUpper());
        }
        [TestMethod]
        public void baseClassTest()
        {
            var obj0 = new internalClass();
            Assert.AreEqual("rahul",obj0.getFirstName());
            Assert.AreEqual("RAHUL", obj0.getBaseStringUpper());
        }
        [TestMethod]
        public void Class2OverrideTest()
        {
            var obj0 = new internalClass.secondClass();
            //Below assertion will fail, as this is overridden
            /*Assert.AreEqual("rahul", obj0.getFirstName());
            Assert.AreEqual("RAHUL", obj0.getBaseStringUpper());*/
            Assert.AreEqual("John", obj0.getFirstName());
            Assert.AreEqual("JOHN", obj0.getBaseStringUpper());
        }
    }
}

Makes sense, right. Therefore, when you run all the tests, it will flag tests as green as shown below in the screenshot.

Passed_Tests

There are many other tests other than these, that's why I have not highlighted the same. Thanks for joining me.

This article was originally posted at http://myview.rahulnivi.net?p=2274

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Publicis Sapient
India India
Hey there, it's Rahul Sahay! I'm thrilled to be a platform specialist at Publicis Sapient, where I get to work on some exciting projects. I've been honing my skills in various aspects of the software development life cycle for more than 15 years, with a primary focus on web stack development. I've been fortunate to have contributed to numerous software development initiatives, ranging from client applications to web services and websites. Additionally, I enjoy crafting application architecture from scratch, and I've spent most of my time writing platform agnostic and cloud agnostic code. As a self-proclaimed code junkie, software development is more than just a job to me; it's a passion! And I consider myself lucky to have worked with an array of cutting-edge technologies, from .NetCore to SpringBoot 3, from Angular to React, and from Azure to AWS and many more cousin technologies...

- 🔭 I’m currently working @ below tech stacks
- Microservices,
- Distributed Systems,
- Spring Boot
- Spring Cloud
- System Design,
- Docker,
- Kubernetes,
- Message Queues,
- ELK Stack
- DotNetCore,
- Angular,
- Azure

- 💬 Ask me anything about my articles [My View](https://myview.rahulnivi.net/)
- 📫 How to reach me: [@rahulsahay19](https://twitter.com/rahulsahay19)
- 📫 Github: [@rahulsahay19](https://github.com/rahulsahay19)

Comments and Discussions

 
SuggestionWhat's the motivation? Pin
Tomas Takac20-Sep-15 21:46
Tomas Takac20-Sep-15 21:46 
GeneralRe: What's the motivation? Pin
rahulsahay204-Oct-15 8:25
rahulsahay204-Oct-15 8:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.