To start out, I’m going to state that I don’t condone this, but if you need it then you need it. I expect to get some nasty comments about pattern violation this or encapsulation that so go ahead and send them in. This is just an extension of my previous post about modifying 3rd party assemblies and the power of PostSharp.

The Evil API

I was working with an API recently that had me using ILSpy to figure anything out. At some point I needed to make use of some code while inheriting a class and overriding a virtual method. So I copied out some code from ILSpy into Visual Studio when I realized that an important method call was broken because that method was internal only. DOH! Due to reasons I won’t explain, I didn’t actually modify the assembly, I just went another way, but if I could have I would have just exposed the method.

Here is our own little evil API

namespace Example.API
{
    public class Calculator
    {
        public int ProcessValue(int x)
        {
            return SuperSpecialCalculation(x, x);
        }

        internal int SuperSpecialCalculation(int x, int y)
        {
            return x + y;
        }

    }
}

Two methods, a public and an internal. We need access to the internal!

The Apsect and Provider

I’m sure there are better ways, but I decided to just introduce a public method using the same signature as the internal method and it will just act as a wrapper. So we need an aspect that imports the expected internal method (target) and introduces our wrapper method. Then we need a provider to tell PostSharp where to apply our aspect. For more information on aspect providers, see PostSharp Principals day 12 & day 13. For more information on introduction, see PostSharp Principals day 13 & day 14.

namespace InternalToPublic
{
    //Aspect Provider
    public class AspectProvider : IAspectProvider
    {
        public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
        {
            List<AspectInstance> targets = new List<AspectInstance>();

            var targetType = ((Assembly)targetElement).GetTypes().FirstOrDefault(c => c.Name.Equals("Calculator"));

            if (targetType == null) { return targets; }

            targets.Add(new AspectInstance(targetType, new ExposeSuperSpecialCalculation()));

            return targets;
        }

    }

    //Aspect
    [Serializable]
    public class ExposeSuperSpecialCalculation : InstanceLevelAspect
    {
        [ImportMember("SuperSpecialCalculation", IsRequired = true, Order = ImportMemberOrder.BeforeIntroductions)]
        public Func<int, int, int> InternalMethod;

        [IntroduceMember]
        public int SuperSpecialCalculationPublic(int x, int y)
        {
            return InternalMethod(x, y);
        }
    }
}

Modify!

Just as in the previous post, we run a batch file to do the modifications to the assembly.

@"C:\Program Files (x86)\PostSharp 2.1\Release\postsharp.4.0-x86-cil.exe" "Example.API.dll" /p:AspectProviders=InternalToPublic.AspectProvider,InternalToPublic /p:Output=output\Example.API.dll
@copy /y InternalToPublic.dll .\Output
@copy /y PostSharp.dll .\Output
@pause

Test

Now we just need to make sure it’s all working. Create a new project and reference the modified Example.API.dll, InternalToPublic.dll and PostSharp.dll.

namespace Example.TestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Example.API.Calculator c = new API.Calculator();
            Console.WriteLine(c.ProcessValue(10));
            Console.WriteLine(c.SuperSpecialCalculationPublic(10, 20));
            Console.ReadKey();
        }
    }
}

The output is as expected

20
30

kick it on DotNetKicks.com

Advertisements