Uncapsulator 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Uncapsulator --version 1.0.0                
NuGet\Install-Package Uncapsulator -Version 1.0.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Uncapsulator" Version="1.0.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Uncapsulator --version 1.0.0                
#r "nuget: Uncapsulator, 1.0.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Uncapsulator as a Cake Addin
#addin nuget:?package=Uncapsulator&version=1.0.0

// Install Uncapsulator as a Cake Tool
#tool nuget:?package=Uncapsulator&version=1.0.0                

Uncapsulator

Uncapsulator provides a fluent API for .NET reflection that lets you easily access private members of an object or type, by employing a dynamic proxy that implements IDynamicMetaObjectProvider.

Uncapsulator is also useful when you want to dynamically invoke (public) members of an interface. Ordinary dynamic binding is unreliable in this scenario, because it fails with explicitly implemented interface members.

Uncapsulator is like ReflectionMagic on steroids, and is based on the built-in feature in LINQPad, written by Joseph Albahari.

Getting Started

Uncapsulator can be downloaded via the NuGet package of the same name.

To get started, add the following imports to your source:

using Uncapsulator
using static Uncapsulator.TypeUncapsulator

Then to reflect over an object, call Uncapsulate():

static void Main()
{
    var demo = new Demo();
    int privateValue = demo.Uncapsulate()._private;
}

class Demo
{
    int _private = 123;
}

You can keep 'dotting' in to access more private members:

static void Main()
{
    var demo = new Demo();
    int privateValue = demo.Uncapsulate().SomeProp.SomeMethod()._private;
}

class Demo
{
    int _private = 123;
    Demo SomeProp => new Demo();
    Demo SomeMethod() => new Demo();
}

Unwrapping the value

In the end, you'll probably need to extract an underlying value. You can do so with either an implicit or explicit cast:

static void Main()
{
    var uncap = new Demo().Uncapsulate();
    
    DateTime thisWorks = uncap.Now;             // Implicit cast to DateTime
    var thisAlsoWorks = (DateTime) uncap.Now;   // Explicit cast to DateTime
    
    // If you don't know the type and just want a System.Object, call .ToObject():
    
    object obj = uncap.Now.ToObject();
}

class Demo
{
    private DateTime Now => DateTime.Now;
}

Calling methods and indexers

Unencapsulator takes care of method overload resolution, numeric conversions, ref/out marshaling, optional parameters, and generic methods:

static void Main()
{
    // Calling an object's private methods is easy with Uncapsulate():
    var demo = new Demo().Uncapsulate();
    string result1 = demo.PrivateMethod (100);

    // The uncapsulator will perform implicit numeric conversions for you automatically:
    string result2 = demo.PrivateMethod ((byte)100);

    // It will also perform overload resolution:
    string result3 = demo.PrivateMethod ("some string");

    // ...even with ref and out parameters:
    demo.PrivateMethod (out string s);
    
    // Optional parameters are also supported:
    demo.OptionalParamMethod (100);
    
    // as are indexers:
    string result4 = demo[123]; 
    
    // You can also call generic methods, as long as you're able to specify type parameters:
    demo.GenericMethod<DateTime>();
}

class Demo
{
    string PrivateMethod (int x) => "Private method! (int) " + x;

    string PrivateMethod (string s) => "Private method! (string) " + s;

    void PrivateMethod (out string x) => x = "Passing by reference works!";
    
    void OptionalParamMethod (int x = 1, int y = 2, int z = 3)
        => Console.WriteLine (new { x, y, z }));
        
    string this [int index] => index.ToString();

    void GenericMethod<T>() => Console.WriteLine (typeof (T).FullName);
}

Static members

You can access static members of a type via methods on the TypeEncapsulator class:

using Uncapsulator;
using static Uncapsulator.TypeUncapsulator;   // This makes the Uncapsulate function easy to call.

static void Main()
{
    string result1 = Uncapsulate<Demo>()._privateField;

    // If the type that you want to access is private, you can specify the type name as a string:
    string result2 = Uncapsulate ("Demo", Assembly.GetExecutingAssembly())._privateField;

    // Use the + symbol to denote a nested class:
    string result3 = Uncapsulate ("Demo+NestedPrivate", Assembly.GetExecutingAssembly())._privateField;

    // Or if the containing class is accessible:
    Uncapsulate<Demo>().NestedPrivate._privateField.Dump();
}

class Demo
{
    static string _privateField = "static private value";

    class NestedPrivate
    {
        static string _privateField = "static private nested value";
    }    
}

Constructing a new object

Constructing a new object is just like calling a static member (see preceding sample) whose name is @new

static void Main()
{
    Demo myClass = Uncapsulate<Demo>().@new (1);
    
    // or if the type is inaccessible:
    var myClass2 = Uncapsulate ("Demo").@new (2);    
    
    // You can also use @new as an instance method (i.e., on an existing object).
    // It will then instantiate a new object of the same type.
    myClass.Uncapsulate().@new (3);
}

class Demo
{
    static string _privateField = "static private value";

    private Demo (int foo) => Console.WriteLine ("Private constructor! " + foo);
}

Interfaces and casts

To call an explicitly implemented interface method, first cast to the interface with CastTo()

static void Main()
{
    new Demo().Uncapsulate().CastTo<ISomeInterface>().Test();
    
    // OR:
    new Demo().Uncapsulate().CastTo (typeof (ISomeInterface)).Test();

    // If the interface is private, specify its name as a string:
    new Demo().Uncapsulate().CastTo ("ISomeInterface").Test();

    // To cast to a generic type, specify the type name with a backtick:
    int count = new int[123].Uncapsulate().CastTo ("IList`1").Count;
}

interface ISomeInterface       { void Test();                  }
class Demo : ISomeInterface    { void ISomeInterface.Test() {} }

Uncapsulate() is useful even with public interfaces, as an alternative to C#'s standard dynamic binding. This is because the latter does not let you call explicitly implemented members:

void Main()
{

    dynamic foo = new Demo();
    try                  { foo.Test();               }
    catch (Exception ex) { ex.Dump ("Not allowed!"); }
    
    // The same thing works nicely with Uncapsulate:
    var foo2 = new Demo().Uncapsulate();
    foo2.CastTo<ISomeInterface>().Test();     // Works!	
    foo2.CastTo("ISomeInterface").Test();     // Works!	
}

public interface ISomeInterface       { void Test();                  }
public class Demo : ISomeInterface    { void ISomeInterface.Test() {} }

Calling members hidden by a subtype

Should a subclass and base class define members with the same name, the subclass will normally "win" and hide the base class's member. To access the base class member, use the special @base member:

static void Main()
{    
    var sub = new SubClass();
        
    string s1 = sub.Uncapsulate()._x;          // "subclass"
    string s2 = sub.Uncapsulate().@base._x;    // "base class"

    // You can also access the base member with a cast:	
    string s3 = sub.Uncapsulate().CastTo ("BaseClass")._x;
}

class BaseClass
{
    string _x = "base class";
}

class SubClass : BaseClass
{
    string _x = "subclass";
}

Using LINQ

To query a collection of private objects, call ToDynamicSequence() on the collection and assign the result to IEnumerable<dynamic>:

static void Main()
{
    IEnumerable<dynamic> sequence = new Demo().Uncapsulate().Customers.ToDynamicSequence();
    
    // Now we have something that we can run LINQ queries over:    
    var query =
        from item in sequence
        orderby (string) item._lastName   // Remember to cast the elements!
        select item;    
}

class Demo
{
    Customer[] Customers => Enumerable.Range (0, 6).Select (_ => new Customer()).ToArray();

    class Customer
    {
        string _firstName = "Joe" + _random.Next (10);
        string _lastName = "Bloggs" + _random.Next (10);

        static Random _random = new Random();
    }
}
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.0.0 28,216 10/26/2021
1.0.1 380 3/16/2021
1.0.0 552 6/26/2020
1.0.0-beta.3 284 6/1/2020