├── .gitignore ├── Assets └── Images │ ├── method-descriptor.gif │ ├── methodtable-fixed-layout.png │ ├── methodtable_layout.gif │ └── methodtable_layout_example.gif ├── LICENSE ├── MethodRedirect.sln ├── README.md ├── Sources ├── Extensions.cs ├── MethodOperation.cs ├── MethodRedirect.csproj ├── MethodToken.cs ├── Properties │ └── AssemblyInfo.cs ├── Scenario1.cs ├── Scenario2.cs ├── Scenario3.cs ├── Scenario4.cs ├── Scenario5.cs └── Scenario6.cs └── UnitTests ├── MethodRedirect_UT.csproj ├── Properties └── AssemblyInfo.cs ├── Scenario1_UnitTests.cs ├── Scenario2_UnitTests.cs ├── Scenario3_UnitTests.cs ├── Scenario4_UnitTests.cs ├── Scenario5_UnitTests.cs └── Scenario6_UnitTests.cs /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | TestResults/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | 84 | # Visual Studio profiler 85 | *.psess 86 | *.vsp 87 | *.vspx 88 | *.sap 89 | 90 | # TFS 2012 Local Workspace 91 | $tf/ 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | *.DotSettings.user 100 | 101 | # JustCode is a .NET coding add-in 102 | .JustCode 103 | 104 | # TeamCity is a build add-in 105 | _TeamCity* 106 | 107 | # DotCover is a Code Coverage Tool 108 | *.dotCover 109 | 110 | # NCrunch 111 | _NCrunch_* 112 | .*crunch*.local.xml 113 | nCrunchTemp_* 114 | 115 | # MightyMoose 116 | *.mm.* 117 | AutoTest.Net/ 118 | 119 | # Web workbench (sass) 120 | .sass-cache/ 121 | 122 | # Installshield output folder 123 | [Ee]xpress/ 124 | 125 | # DocProject is a documentation generator add-in 126 | DocProject/buildhelp/ 127 | DocProject/Help/*.HxT 128 | DocProject/Help/*.HxC 129 | DocProject/Help/*.hhc 130 | DocProject/Help/*.hhk 131 | DocProject/Help/*.hhp 132 | DocProject/Help/Html2 133 | DocProject/Help/html 134 | 135 | # Click-Once directory 136 | publish/ 137 | 138 | # Publish Web Output 139 | *.[Pp]ublish.xml 140 | *.azurePubxml 141 | # TODO: Comment the next line if you want to checkin your web deploy settings 142 | # but database connection strings (with potential passwords) will be unencrypted 143 | *.pubxml 144 | *.publishproj 145 | 146 | # NuGet Packages 147 | *.nupkg 148 | # The packages folder can be ignored because of Package Restore 149 | **/packages/* 150 | # except build/, which is used as an MSBuild target. 151 | !**/packages/build/ 152 | # Uncomment if necessary however generally it will be regenerated when needed 153 | #!**/packages/repositories.config 154 | # NuGet v3's project.json files produces more ignoreable files 155 | *.nuget.props 156 | *.nuget.targets 157 | 158 | # Microsoft Azure Build Output 159 | csx/ 160 | *.build.csdef 161 | 162 | # Microsoft Azure Emulator 163 | ecf/ 164 | rcf/ 165 | 166 | # Microsoft Azure ApplicationInsights config file 167 | ApplicationInsights.config 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | ~$* 182 | *~ 183 | *.dbmdl 184 | *.dbproj.schemaview 185 | *.pfx 186 | *.publishsettings 187 | node_modules/ 188 | orleans.codegen.cs 189 | 190 | # RIA/Silverlight projects 191 | Generated_Code/ 192 | 193 | # Backup & report files from converting an old project file 194 | # to a newer Visual Studio version. Backup files are not needed, 195 | # because we have git ;-) 196 | _UpgradeReport_Files/ 197 | Backup*/ 198 | UpgradeLog*.XML 199 | UpgradeLog*.htm 200 | 201 | # SQL Server files 202 | *.mdf 203 | *.ldf 204 | 205 | # Business Intelligence projects 206 | *.rdl.data 207 | *.bim.layout 208 | *.bim_*.settings 209 | 210 | # Microsoft Fakes 211 | FakesAssemblies/ 212 | 213 | # GhostDoc plugin setting file 214 | *.GhostDoc.xml 215 | 216 | # Node.js Tools for Visual Studio 217 | .ntvs_analysis.dat 218 | 219 | # Visual Studio 6 build log 220 | *.plg 221 | 222 | # Visual Studio 6 workspace options file 223 | *.opt 224 | 225 | # Visual Studio LightSwitch build output 226 | **/*.HTMLClient/GeneratedArtifacts 227 | **/*.DesktopClient/GeneratedArtifacts 228 | **/*.DesktopClient/ModelManifest.xml 229 | **/*.Server/GeneratedArtifacts 230 | **/*.Server/ModelManifest.xml 231 | _Pvt_Extensions 232 | 233 | # Paket dependency manager 234 | .paket/paket.exe 235 | 236 | # FAKE - F# Make 237 | .fake/ 238 | 239 | /Manual/build/html 240 | /Manual/build/doctrees 241 | /Manual/build/pdf 242 | /Manual/build/latex 243 | /Manual/source/_fonts/* 244 | /Manual/.venv 245 | /Manual/debug.log 246 | /Manual/web.config 247 | /Manual/web.Debug.config 248 | /Manual/web.Release.config 249 | /Setup/MSI 250 | /Setup/Zip/*.zip 251 | 252 | -------------------------------------------------------------------------------- /Assets/Images/method-descriptor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinico/MethodRedirect/1e2a681c22dc3781cfd36a8f683c3b808456f429/Assets/Images/method-descriptor.gif -------------------------------------------------------------------------------- /Assets/Images/methodtable-fixed-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinico/MethodRedirect/1e2a681c22dc3781cfd36a8f683c3b808456f429/Assets/Images/methodtable-fixed-layout.png -------------------------------------------------------------------------------- /Assets/Images/methodtable_layout.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinico/MethodRedirect/1e2a681c22dc3781cfd36a8f683c3b808456f429/Assets/Images/methodtable_layout.gif -------------------------------------------------------------------------------- /Assets/Images/methodtable_layout_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinico/MethodRedirect/1e2a681c22dc3781cfd36a8f683c3b808456f429/Assets/Images/methodtable_layout_example.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nicolas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MethodRedirect.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30717.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MethodRedirect", "Sources\MethodRedirect.csproj", "{2A84DC12-471E-492F-B390-DF3ADB452E28}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5D6D3267-F44E-40E2-ADC3-20DDC4DE3E39}" 9 | ProjectSection(SolutionItems) = preProject 10 | LICENSE = LICENSE 11 | README.md = README.md 12 | EndProjectSection 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Images", "Images", "{E3BA3202-B3CC-41C8-ABF6-ED10A792DB4C}" 15 | ProjectSection(SolutionItems) = preProject 16 | Assets\Images\method-descriptor.gif = Assets\Images\method-descriptor.gif 17 | Assets\Images\methodtable-fixed-layout.png = Assets\Images\methodtable-fixed-layout.png 18 | Assets\Images\methodtable_layout.gif = Assets\Images\methodtable_layout.gif 19 | Assets\Images\methodtable_layout_example.gif = Assets\Images\methodtable_layout_example.gif 20 | EndProjectSection 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MethodRedirect_UT", "UnitTests\MethodRedirect_UT.csproj", "{44352427-E1E0-4679-BBBD-0E298B2BAEB4}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Debug|x64 = Debug|x64 28 | Debug|x86 = Debug|x86 29 | Release|Any CPU = Release|Any CPU 30 | Release|x64 = Release|x64 31 | Release|x86 = Release|x86 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {2A84DC12-471E-492F-B390-DF3ADB452E28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {2A84DC12-471E-492F-B390-DF3ADB452E28}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {2A84DC12-471E-492F-B390-DF3ADB452E28}.Debug|x64.ActiveCfg = Debug|x64 37 | {2A84DC12-471E-492F-B390-DF3ADB452E28}.Debug|x64.Build.0 = Debug|x64 38 | {2A84DC12-471E-492F-B390-DF3ADB452E28}.Debug|x86.ActiveCfg = Debug|x86 39 | {2A84DC12-471E-492F-B390-DF3ADB452E28}.Debug|x86.Build.0 = Debug|x86 40 | {2A84DC12-471E-492F-B390-DF3ADB452E28}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {2A84DC12-471E-492F-B390-DF3ADB452E28}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {2A84DC12-471E-492F-B390-DF3ADB452E28}.Release|x64.ActiveCfg = Release|x64 43 | {2A84DC12-471E-492F-B390-DF3ADB452E28}.Release|x64.Build.0 = Release|x64 44 | {2A84DC12-471E-492F-B390-DF3ADB452E28}.Release|x86.ActiveCfg = Release|x86 45 | {2A84DC12-471E-492F-B390-DF3ADB452E28}.Release|x86.Build.0 = Release|x86 46 | {44352427-E1E0-4679-BBBD-0E298B2BAEB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {44352427-E1E0-4679-BBBD-0E298B2BAEB4}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {44352427-E1E0-4679-BBBD-0E298B2BAEB4}.Debug|x64.ActiveCfg = Debug|x64 49 | {44352427-E1E0-4679-BBBD-0E298B2BAEB4}.Debug|x64.Build.0 = Debug|x64 50 | {44352427-E1E0-4679-BBBD-0E298B2BAEB4}.Debug|x86.ActiveCfg = Debug|x86 51 | {44352427-E1E0-4679-BBBD-0E298B2BAEB4}.Debug|x86.Build.0 = Debug|x86 52 | {44352427-E1E0-4679-BBBD-0E298B2BAEB4}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {44352427-E1E0-4679-BBBD-0E298B2BAEB4}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {44352427-E1E0-4679-BBBD-0E298B2BAEB4}.Release|x64.ActiveCfg = Release|x64 55 | {44352427-E1E0-4679-BBBD-0E298B2BAEB4}.Release|x64.Build.0 = Release|x64 56 | {44352427-E1E0-4679-BBBD-0E298B2BAEB4}.Release|x86.ActiveCfg = Release|x86 57 | {44352427-E1E0-4679-BBBD-0E298B2BAEB4}.Release|x86.Build.0 = Release|x86 58 | EndGlobalSection 59 | GlobalSection(SolutionProperties) = preSolution 60 | HideSolutionNode = FALSE 61 | EndGlobalSection 62 | GlobalSection(NestedProjects) = preSolution 63 | {E3BA3202-B3CC-41C8-ABF6-ED10A792DB4C} = {5D6D3267-F44E-40E2-ADC3-20DDC4DE3E39} 64 | EndGlobalSection 65 | GlobalSection(ExtensibilityGlobals) = postSolution 66 | SolutionGuid = {CD65340E-E5E2-4031-B5C2-B7024FA40B35} 67 | EndGlobalSection 68 | EndGlobal 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## MethodRedirect 2 | 3 | MethodRedirect is a `MethodInfo` extension written in C# that can be used to redirect a method call to another using reflection. 4 | 5 | This implementation uses marshalling to modify the address of the corresponding *Method Descriptor* without the need to use *unsafe* block. 6 | 7 | This project was inspired by [one of the answers](https://stackoverflow.com/a/55026523/5953306) given for [this question](https://stackoverflow.com/questions/7299097/dynamically-replace-the-contents-of-a-c-sharp-method) on StackOverflow about replacing the content of a C# method. The answer did not provide sufficient explanation on how it actually works and neither shows an example of its usage. The following are development notes and references used to implement this project and hopefully explain how it works too. 8 | 9 | ##### From the CLR documentation: 10 | 11 | - Each MethodDesc has a slot, which contains the entry point of the method. The slot and entry point must exist for all methods, even the ones that never run like abstract methods. 12 | 13 | - The slot is either in `MethodTable` or in `MethodDesc` itself. The location of the slot is determined by `mdcHasNonVtableSlot` bit on `MethodDesc`. 14 | 15 | - The slot is stored in `MethodTable` for methods that require efficient lookup via slot index, e.g. virtual methods or methods on generic types. The `MethodDesc` contains the slot index to allow fast lookup of the entry point in this case. 16 | 17 | - Each class and interface will be represented in memory by a `MethodTable` (MT) data structure. A pointer to the `MethodTable` can be acquired through the `Type.RuntimeTypeHandle` property. The `TypeHandle` (contained in the `ObjectInstance`) points to an offset from the beginning of the `MethodTable`. This offset is 12 bytes by default. 18 | 19 | ##### MethodTable 20 | 21 | ![MethodTable Layout](./Assets/Images/methodtable_layout.gif) 22 | 23 | Embedded within the `MethodTable` is a table of slots that point to the respective method descriptors (`MethodDesc`), enabling the behavior of the type. The `Method Slot Table` is created based on the linearized list of implementation methods laid out in the following order: 24 | 25 | 1. Inherited virtuals 26 | 2. Introduced virtuals 27 | 3. Instance methods 28 | 4. Static methods 29 | 30 | **The first four methods of any type will always be `ToString()`, `Equals()`, `GetHashCode()`, and `Finalize()`** in this order. On x86, each method's address is represented on 4 bytes. On x64 build, each method's address is represented on 8 bytes. 31 | 32 | - On x86 build, the fixed-size portion of `MethodTable` is 40 bytes. 33 | - On x64 build, the fixed-size portion of `MethodTable` is 64 bytes. 34 | 35 | The object constructor (.ctor) is automatically generated by the C# compiler for all objects having no constructor explicitly defined. 36 | 37 | The class constructor (.cctor) is generated by the C# compiler when at least one static variable is defined. 38 | 39 | ##### MethodTable Layout Example 40 | 41 | ![MethodTable Layout Example](./Assets/Images/methodtable_layout_example.gif) 42 | 43 | ![MethodTable Layout](./Assets/Images/methodtable-fixed-layout.png) 44 | 45 | ##### Method Descriptor 46 | 47 | ![Method Descriptor](./Assets/Images/method-descriptor.gif) 48 | 49 | - Method Descriptor (`MethodDesc`) is an encapsulation of method implementation as the CLR knows it. 50 | 51 | - Each `MethodDesc` is padded with a `PreJitStub`, which is responsible for triggering JIT compilation. 52 | 53 | - The `Method Table Slot` entry actually points to the stub instead of the actual `MethodDesc` data structure. This is at a negative offset of 5 bytes from the actual `MethodDesc` and is part of **the 8-byte padding every method inherits**. 54 | 55 | - `MethodDesc` is always 5 bytes after the location pointed by the `Method Table Slot` entry. After the compilation is complete, the 5 bytes containing the call instruction will be overwritten with an unconditional jump to the JIT-compiled code. 56 | 57 | - The `Flags` field in the method descriptor is encoded to contain the information about the type of the method, such as static, instance, interface method, or COM implementation. The `Flags` field is represented on 3-bit [0-7] and can be one of the [MethodClassification](https://github.com/dotnet/coreclr/blob/master/src/vm/method.hpp#L90) enumeration value. 58 | 59 | 60 | #### Remarks 61 | 62 | - Calling a redirected static method using a direct function call may return a cached result. An alternative approach is to call the static function using the `Invoke` method of the corresponding `MethodInfo`. 63 | 64 | - For direct normal method call, the `MethodRedirection` instance returned from a call to the `RedirectTo` method can be used to revert the method redirection to the original address. However, it is not actually possible to revert a redirected method call that is made **within** another already compiled (JITted) method since the address of the redirected method call will remain unchanged in the compiled method. See the unit tests of **Scenario5** for more details on this behavior. 65 | 66 | - Using lambda expression (see **Scenario6**'s unit tests), the method redirection works when the origin method is declared as `virtual`. 67 | 68 | #### References 69 | 70 | - [.NET Framework Internals: How the CLR Creates Runtime Objects](https://docs.microsoft.com/en-us/archive/msdn-magazine/2005/may/net-framework-internals-how-the-clr-creates-runtime-objects) 71 | - [Method Descriptor](https://github.com/dotnet/coreclr/blob/master/Documentation/botr/method-descriptor.md) 72 | - [Dynamically replace the contents of a C# method?](https://stackoverflow.com/questions/7299097/dynamically-replace-the-contents-of-a-c-sharp-method) 73 | - [The VTABLE](https://github.com/dotnet/coreclr/blob/master/src/vm/methodtable.h#L1464) 74 | - [CLR implementation of virtual method calls to interface members](https://stackoverflow.com/questions/9808982/clr-implementation-of-virtual-method-calls-to-interface-members) 75 | - [Exploiting C++ VTABLES: Instance Replacement](https://defuse.ca/exploiting-cpp-vtables.htm) 76 | - ["Stubs" in the .NET Runtime](https://mattwarren.org/2019/09/26/Stubs-in-the-.NET-Runtime/) 77 | - [Custom memory allocation in C#](https://blog.adamfurmanek.pl/2016/05/07/custom-memory-allocation-in-c-part-3/) 78 | - [CLR runtime details-Method Descriptor](https://www.programmersought.com/article/53334314040/) 79 | 80 | --- 81 | 82 | ## [MIT](http://opensource.org/licenses/MIT) License 83 | 84 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 85 | 86 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 87 | 88 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Sources/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reflection; 4 | using System.Runtime.CompilerServices; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace MethodRedirect 8 | { 9 | static class Extensions 10 | { 11 | public static MethodOperation RedirectTo(this MethodInfo origin, Func target, bool verbose = false) 12 | => RedirectTo(origin, target.Method, verbose); 13 | 14 | public static MethodOperation RedirectTo(this MethodInfo origin, Func target, bool verbose = false) 15 | => RedirectTo(origin, target.Method, verbose); 16 | 17 | /// 18 | /// Redirect origin method calls to the specified target method. 19 | /// Use redirection when there is no need to call the origin method when redirected to the target method. 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// 25 | /// A MethodRedirection operation result object 26 | /// 27 | public static MethodOperation RedirectTo(this MethodInfo origin, MethodInfo target, bool verbose = false) 28 | { 29 | IntPtr ori = GetMethodAddress(origin); 30 | IntPtr tar = GetMethodAddress(target); 31 | 32 | // The function pointer gives the value located at the method address... 33 | // This can be used for validation that the method address matches 34 | Debug.Assert(Marshal.ReadIntPtr(ori) == origin.MethodHandle.GetFunctionPointer()); 35 | Debug.Assert(Marshal.ReadIntPtr(tar) == target.MethodHandle.GetFunctionPointer()); 36 | 37 | if (verbose) 38 | { 39 | Console.WriteLine("\nPlatform : {0}", IntPtr.Size == 4 ? "x86" : "x64"); 40 | Console.WriteLine("IntPtr.Size : {0}", IntPtr.Size); 41 | 42 | Console.WriteLine("\nFrom origin method : {0}", origin.Name); 43 | OutputMethodDetails(origin, ori); 44 | 45 | Console.WriteLine("\nTo target method : {0}", target.Name); 46 | OutputMethodDetails(target, tar); 47 | } 48 | 49 | return Redirect(ori, tar, verbose); 50 | } 51 | 52 | /// 53 | /// Show the details of the method address evaluation 54 | /// 55 | /// 56 | /// 57 | /// The unconditional jmp address to the JIT-compiled method 58 | /// 59 | private static void OutputMethodDetails(MethodInfo mi, IntPtr address) 60 | { 61 | IntPtr mt = mi.DeclaringType.TypeHandle.Value; // MethodTable address 62 | IntPtr md = mi.MethodHandle.Value; // MethodDescriptor address 63 | 64 | // MethodTable address > MethodDescriptor address 65 | int offset = (int)((long)mt - (long)md); 66 | 67 | Console.WriteLine("Method is virtual : {0}", mi.IsVirtual); 68 | Console.WriteLine("MethodDescriptor (MD) : \t{0}", md.ToString("x").PadLeft(8, '0')); 69 | Console.WriteLine("MethodTable (MT) : \t{0}", mt.ToString("x").PadLeft(8,'0')); 70 | Console.WriteLine("Offset (MT - MD) : \t{0}", offset.ToString("x").PadLeft(8, '0')); 71 | 72 | if (mi.IsVirtual) 73 | { 74 | // The fixed-size portion of the MethodTable structure depends on the process type: 75 | // For 32-bit process (IntPtr.Size == 4), the fixed-size portion is 40 (0x28) bytes 76 | // For 64-bit process (IntPtr.Size == 8), the fixed-size portion is 64 (0x40) bytes 77 | offset = IntPtr.Size == 4 ? 0x28 : 0x40; 78 | 79 | // First method slot = MethodTable address + fixed-size offset 80 | // This is the address of the first method of any type (i.e. ToString) 81 | IntPtr ms = mt + offset; 82 | 83 | Console.WriteLine("MethodTable offset : \t{0}", offset.ToString("x").PadLeft(8, '0')); 84 | Console.WriteLine("First method slot : {0}\t{1}", 85 | Marshal.ReadIntPtr(ms).ToString("x").PadLeft(8,'0'), 86 | ms.ToString("x").PadLeft(8,'0')); 87 | 88 | // To get the slot number of the virtual method entry from the MethodDesc data structure 89 | // 90 | // a. Get the value at the address of the MethodDescriptor 91 | // 92 | // MethodDesc data structure 93 | // --------------------------- 94 | // MethodDescriptor -> | Token Remainder | 2 bytes 95 | // | Chunck Index | 1 bytes 96 | // MethodTableSlot -> | Stub (op code + target) | 5 bytes 97 | // | Slot Number | 2 bytes 98 | // | Flags | 2 bytes 99 | // | CodeOrIL | 4 bytes 100 | // --------------------------- 101 | // 102 | // b. Right shift the value by 8 bytes (32 bits) 103 | // c. Mask the slot number field to get its value 104 | 105 | long shift = Marshal.ReadInt64(md) >> 32; 106 | ushort mask = 0xffff; // 16 bit (2 bytes) mask for the slot number value 107 | int slot = (int)(shift & mask); 108 | 109 | Console.WriteLine("\nMethodDesc data : {0}", Marshal.ReadInt64(md).ToString("x").PadLeft(8,'0')); 110 | Console.WriteLine("Right-shift 32 bits : {0}", shift.ToString("x").PadLeft(8,'0')); 111 | Console.WriteLine("Mask : {0}", mask.ToString("x").PadLeft(8,'0')); 112 | Console.WriteLine("Method slot number : {0}", slot); 113 | 114 | Console.WriteLine("\nMethodDesc data : {0}", Convert.ToString(Marshal.ReadInt64(md), 2).PadLeft(32, '0')); 115 | Console.WriteLine("Right-shift 32 bits : {0}", Convert.ToString(shift, 2).PadLeft(32, '0')); 116 | Console.WriteLine("Mask : {0}", Convert.ToString(mask, 2).PadLeft(32, '0')); 117 | Console.WriteLine("Method slot number : {0}\n", Convert.ToString(slot,2).PadLeft(32, '0')); 118 | 119 | Console.WriteLine("Relative offset : {0}", (IntPtr.Size * slot).ToString("x").PadLeft(8,'0')); 120 | } 121 | 122 | offset = (int)((long)address - (long)mt); 123 | 124 | Console.WriteLine("Jitted method (JM) : {0}\t{1}", address.ToString("x").PadLeft(8,'0'), 125 | Marshal.ReadIntPtr(address).ToString("x").PadLeft(8,'0')); 126 | 127 | Console.WriteLine("Offset (JM - MT) : {0}", offset); 128 | } 129 | 130 | /// 131 | /// Obtain the unconditional jump address to the JIT-compiled method 132 | /// 133 | /// 134 | /// 135 | /// Before JIT compilation: 136 | /// - call to PreJITStub to initiate compilation. 137 | /// - the CodeOrIL field contains the Relative Virtual Address (IL RVA) of the method implementation in IL. 138 | /// 139 | /// After on-demand JIT compilation: 140 | /// - CRL changes the call to the PreJITStub for an unconditional jump to the JITed method. 141 | /// - the CodeOrIL field contains the Virtual Address (VA) of the JIT-compiled method. 142 | /// 143 | /// The JITed method address 144 | private static IntPtr GetMethodAddress(MethodInfo mi) 145 | { 146 | const ushort SLOT_NUMBER_MASK = 0xffff; // 2 bytes mask 147 | const int MT_OFFSET_32BIT = 0x28; // 40 bytes offset 148 | const int MT_OFFSET_64BIT = 0x40; // 64 bytes offset 149 | 150 | IntPtr address; 151 | 152 | // JIT compilation of the method 153 | RuntimeHelpers.PrepareMethod(mi.MethodHandle); 154 | 155 | IntPtr md = mi.MethodHandle.Value; // MethodDescriptor address 156 | IntPtr mt = mi.DeclaringType.TypeHandle.Value; // MethodTable address 157 | 158 | if (mi.IsVirtual) 159 | { 160 | // The fixed-size portion of the MethodTable structure depends on the process type: 161 | // For 32-bit process (IntPtr.Size == 4), the fixed-size portion is 40 (0x28) bytes 162 | // For 64-bit process (IntPtr.Size == 8), the fixed-size portion is 64 (0x40) bytes 163 | int offset = IntPtr.Size == 4 ? MT_OFFSET_32BIT : MT_OFFSET_64BIT; 164 | 165 | // First method slot = MethodTable address + fixed-size offset 166 | // This is the address of the first method of any type (i.e. ToString) 167 | IntPtr ms = Marshal.ReadIntPtr(mt + offset); 168 | 169 | // Get the slot number of the virtual method entry from the MethodDesc data structure 170 | long shift = Marshal.ReadInt64(md) >> 32; 171 | int slot = (int)(shift & SLOT_NUMBER_MASK); 172 | 173 | // Get the virtual method address relative to the first method slot 174 | address = ms + (slot * IntPtr.Size); 175 | } 176 | else 177 | { 178 | // Bypass default MethodDescriptor padding (8 bytes) 179 | // Reach the CodeOrIL field which contains the address of the JIT-compiled code 180 | address = md + 8; 181 | } 182 | 183 | return address; 184 | } 185 | 186 | private static MethodRedirection Redirect(IntPtr ori, IntPtr tar, bool verbose) 187 | { 188 | // Must create the token before address is assigned 189 | var token = new MethodRedirection(ori); 190 | 191 | if (verbose) 192 | { 193 | Console.WriteLine("\nRedirect..."); 194 | Console.WriteLine("From {0} [{1}] => To {2} [{3}]", 195 | ori.ToString("x").PadLeft(8, '0'), 196 | Marshal.ReadIntPtr(ori).ToString("x").PadLeft(8, '0'), 197 | tar.ToString("x").PadLeft(8, '0'), 198 | Marshal.ReadIntPtr(tar).ToString("x").PadLeft(8, '0')); 199 | } 200 | 201 | // Redirect origin method to target method 202 | Marshal.Copy(new IntPtr[] { Marshal.ReadIntPtr(tar) }, 0, ori, 1); 203 | 204 | return token; 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /Sources/MethodOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MethodRedirect 4 | { 5 | /// 6 | /// Base method operation result 7 | /// 8 | public abstract class MethodOperation : IDisposable 9 | { 10 | public abstract void Restore(); 11 | 12 | public void Dispose() 13 | { 14 | Restore(); 15 | } 16 | } 17 | 18 | /// 19 | /// Result of a method redirection (Origin => *) 20 | /// 21 | public class MethodRedirection : MethodOperation 22 | { 23 | public MethodToken Origin { get; private set; } 24 | 25 | public MethodRedirection(IntPtr address) 26 | { 27 | Origin = new MethodToken(address); 28 | } 29 | 30 | public override void Restore() 31 | { 32 | Origin.Restore(); 33 | } 34 | 35 | public override string ToString() 36 | { 37 | return Origin.ToString(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/MethodRedirect.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2A84DC12-471E-492F-B390-DF3ADB452E28} 8 | Exe 9 | MethodRedirect 10 | MethodRedirect 11 | v4.0 12 | 512 13 | true 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | false 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | false 35 | 36 | 37 | MethodRedirect.Scenario1 38 | 39 | 40 | true 41 | bin\x64\Debug\ 42 | DEBUG;TRACE 43 | true 44 | full 45 | x64 46 | 7.3 47 | prompt 48 | false 49 | 50 | 51 | bin\x64\Release\ 52 | TRACE 53 | true 54 | true 55 | pdbonly 56 | x64 57 | 7.3 58 | prompt 59 | 60 | 61 | true 62 | bin\x86\Debug\ 63 | DEBUG;TRACE 64 | true 65 | full 66 | x86 67 | 7.3 68 | prompt 69 | 70 | 71 | bin\x86\Release\ 72 | TRACE 73 | true 74 | false 75 | pdbonly 76 | x86 77 | 7.3 78 | prompt 79 | true 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /Sources/MethodToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace MethodRedirect 5 | { 6 | public struct MethodToken : IDisposable 7 | { 8 | public IntPtr Address { get; private set; } 9 | public IntPtr Value { get; private set; } 10 | 11 | public MethodToken(IntPtr address) 12 | { 13 | // On token creation, preserve the address and the current value at this address 14 | Address = address; 15 | Value = Marshal.ReadIntPtr(address); 16 | } 17 | 18 | public void Restore() 19 | { 20 | // Restore the value at the address 21 | Marshal.Copy(new IntPtr[] { Value }, 0, Address, 1); 22 | } 23 | 24 | public override string ToString() 25 | { 26 | IntPtr met = Address; 27 | IntPtr tar = Marshal.ReadIntPtr(Address); 28 | IntPtr ori = Value; 29 | 30 | return "Method address = " + met.ToString("x").PadLeft(8, '0') + "\n" + 31 | "Target address = " + tar.ToString("x").PadLeft(8, '0') + "\n" + 32 | "Origin address = " + ori.ToString("x").PadLeft(8, '0'); 33 | } 34 | 35 | public void Dispose() 36 | { 37 | Restore(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Les informations générales relatives à un assembly dépendent de 6 | // l'ensemble d'attributs suivant. Changez les valeurs de ces attributs pour modifier les informations 7 | // associées à un assembly. 8 | [assembly: AssemblyTitle("MethodRedirect")] 9 | [assembly: AssemblyDescription("Extension to redirect a method to or swap it with another method.")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("NSPX Software")] 12 | [assembly: AssemblyProduct("MethodRedirect")] 13 | [assembly: AssemblyCopyright("Copyright © NSPX Software 2020")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Allow unit tests project to access internal methods 18 | [assembly: InternalsVisibleTo("MethodRedirect_UT")] 19 | 20 | // L'affectation de la valeur false à ComVisible rend les types invisibles dans cet assembly 21 | // aux composants COM. Si vous devez accéder à un type dans cet assembly à partir de 22 | // COM, affectez la valeur true à l'attribut ComVisible sur ce type. 23 | [assembly: ComVisible(false)] 24 | 25 | // Le GUID suivant est pour l'ID de la typelib si ce projet est exposé à COM 26 | [assembly: Guid("2a84dc12-471e-492f-b390-df3adb452e28")] 27 | 28 | // Les informations de version pour un assembly se composent des quatre valeurs suivantes : 29 | // 30 | // Version principale 31 | // Version secondaire 32 | // Numéro de build 33 | // Révision 34 | // 35 | // Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de build et de révision par défaut 36 | // en utilisant '*', comme indiqué ci-dessous : 37 | // [assembly: AssemblyVersion("1.0.*")] 38 | [assembly: AssemblyVersion("1.0.0.0")] 39 | [assembly: AssemblyFileVersion("1.0.0.0")] 40 | -------------------------------------------------------------------------------- /Sources/Scenario1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reflection; 4 | 5 | namespace MethodRedirect 6 | { 7 | class Scenario1 8 | { 9 | static void Main(string[] args) 10 | { 11 | Console.WriteLine("Redirect : MethodRedirect.Scenario1.InternalInstanceMethod()"); 12 | Console.WriteLine("To : MethodRedirect.Scenario1.PrivateInstanceMethod()"); 13 | 14 | Assembly assembly = Assembly.GetAssembly(typeof(Scenario1)); 15 | Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario1"); 16 | 17 | MethodInfo Scenario_InternalInstanceMethod = Scenario_Type.GetMethod("InternalInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); 18 | MethodInfo Scenario_PrivateInstanceMethod = Scenario_Type.GetMethod("PrivateInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); 19 | 20 | var token = Scenario_InternalInstanceMethod.RedirectTo(Scenario_PrivateInstanceMethod, true); 21 | 22 | // Using "dynamic" type to resolve the following issue in x64 and Release (with code optimizations) builds. 23 | // 24 | // Symptom: the second call to method InternalInstanceMethod() does not return the expected string value 25 | // 26 | // Cause: the result from the first call to method InternalInstanceMethod() is cached and so the second 27 | // call gets the cached value instead of making the call again. 28 | // 29 | // Remark: for some reason, without "dynamic" type, only the "Debug x86" build configuration would reevaluate 30 | // the second call to InternalInstanceMethod() without using the cached string value. 31 | // 32 | // Also, using the [MethodImpl(MethodImplOptions.NoOptimization)] attribute on the method did not work. 33 | dynamic scenario = (Scenario1)Activator.CreateInstance(Scenario_Type); 34 | 35 | string methodName = scenario.InternalInstanceMethod(); 36 | 37 | Console.WriteLine("Call MethodRedirect.Scenario1.InternalInstanceMethod => {0}", methodName); 38 | 39 | Debug.Assert(methodName == "MethodRedirect.Scenario1.PrivateInstanceMethod"); 40 | 41 | if (methodName == "MethodRedirect.Scenario1.PrivateInstanceMethod") 42 | { 43 | Console.WriteLine("\nRestore..."); 44 | 45 | token.Restore(); 46 | 47 | Console.WriteLine(token); 48 | 49 | methodName = scenario.InternalInstanceMethod(); 50 | 51 | Console.WriteLine("Call MethodRedirect.Scenario1.InternalInstanceMethod => {0}", methodName); 52 | 53 | Debug.Assert(methodName == "MethodRedirect.Scenario1.InternalInstanceMethod"); 54 | 55 | if (methodName == "MethodRedirect.Scenario1.InternalInstanceMethod") 56 | { 57 | Console.WriteLine("\nSUCCESS!"); 58 | } 59 | else 60 | { 61 | Console.WriteLine("\nRestore FAILED"); 62 | } 63 | } 64 | else 65 | { 66 | Console.WriteLine("\nRedirection FAILED"); 67 | } 68 | 69 | Console.ReadKey(); 70 | } 71 | 72 | internal string InternalInstanceMethod() 73 | { 74 | return "MethodRedirect.Scenario1.InternalInstanceMethod"; 75 | } 76 | 77 | private string PrivateInstanceMethod() 78 | { 79 | return "MethodRedirect.Scenario1.PrivateInstanceMethod"; 80 | } 81 | 82 | public string PublicInstanceMethod() 83 | { 84 | return "MethodRedirect.Scenario1.PublicInstanceMethod"; 85 | } 86 | 87 | public static string PublicStaticMethod() 88 | { 89 | return "MethodRedirect.Scenario1.PublicStaticMethod"; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/Scenario2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reflection; 4 | 5 | namespace MethodRedirect 6 | { 7 | class Scenario2 8 | { 9 | static void Main(string[] args) 10 | { 11 | Console.WriteLine("Redirect : MethodRedirect.Scenario2.InternalVirtualInstanceMethod()"); 12 | Console.WriteLine("To : MethodRedirect.Scenario2.PrivateInstanceMethod()"); 13 | 14 | Assembly assembly = Assembly.GetAssembly(typeof(Scenario2)); 15 | Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario2"); 16 | 17 | MethodInfo Scenario_InternalVirtualInstanceMethod = Scenario_Type.GetMethod("InternalVirtualInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); 18 | MethodInfo Scenario_PrivateInstanceMethod = Scenario_Type.GetMethod("PrivateInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); 19 | 20 | var token = Scenario_InternalVirtualInstanceMethod.RedirectTo(Scenario_PrivateInstanceMethod, true); 21 | 22 | var scenario = (Scenario2)Activator.CreateInstance(Scenario_Type); 23 | 24 | string methodName = scenario.InternalVirtualInstanceMethod(); 25 | 26 | Console.WriteLine("Call MethodRedirect.Scenario2.InternalVirtualInstanceMethod => {0}", methodName); 27 | 28 | Debug.Assert(methodName == "MethodRedirect.Scenario2.PrivateInstanceMethod"); 29 | 30 | if (methodName == "MethodRedirect.Scenario2.PrivateInstanceMethod") 31 | { 32 | Console.WriteLine("\nRestore..."); 33 | 34 | token.Restore(); 35 | 36 | methodName = scenario.InternalVirtualInstanceMethod(); 37 | 38 | Console.WriteLine("Call MethodRedirect.Scenario2.InternalVirtualInstanceMethod => {0}", methodName); 39 | 40 | Debug.Assert(methodName == "MethodRedirect.Scenario2.InternalVirtualInstanceMethod"); 41 | 42 | if (methodName == "MethodRedirect.Scenario2.InternalVirtualInstanceMethod") 43 | { 44 | Console.WriteLine("\nSUCCESS!"); 45 | } 46 | else 47 | { 48 | Console.WriteLine("\nRestore FAILED"); 49 | } 50 | } 51 | else 52 | { 53 | Console.WriteLine("\nRedirection FAILED"); 54 | } 55 | 56 | Console.ReadKey(); 57 | } 58 | 59 | internal virtual string AnotherInternalVirtualInstanceMethod() 60 | { 61 | return "MethodRedirect.Scenario2.AnotherInternalVirtualInstanceMethod"; 62 | } 63 | 64 | internal virtual string InternalVirtualInstanceMethod() 65 | { 66 | return "MethodRedirect.Scenario2.InternalVirtualInstanceMethod"; 67 | } 68 | 69 | private string PrivateInstanceMethod() 70 | { 71 | return "MethodRedirect.Scenario2.PrivateInstanceMethod"; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/Scenario3.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MethodRedirect 4 | { 5 | class Scenario3 6 | { 7 | static void Main(string[] args) 8 | { 9 | Console.ReadKey(); 10 | } 11 | 12 | internal virtual string InternalVirtualInstanceMethod() 13 | { 14 | return "MethodRedirect.Scenario3.InternalVirtualInstanceMethod"; 15 | } 16 | } 17 | 18 | class Scenario3Ext 19 | { 20 | internal static string InternalStaticMethod() 21 | { 22 | return "MethodRedirect.Scenario3Ext.InternalStaticMethod"; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Scenario4.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MethodRedirect 4 | { 5 | class Scenario4 6 | { 7 | static void Main(string[] args) 8 | { 9 | Console.ReadKey(); 10 | } 11 | 12 | public virtual string PublicVirtualInstanceMethod() 13 | { 14 | return "MethodRedirect.Scenario4.PublicVirtualInstanceMethod"; 15 | } 16 | } 17 | 18 | class Scenario4Ext 19 | { 20 | private string PrivateInstanceMethod() 21 | { 22 | return "MethodRedirect.Scenario4Ext.PrivateInstanceMethod"; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Scenario5.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MethodRedirect 4 | { 5 | class Scenario5: Scenario5Base 6 | { 7 | double _customFee = 0.2; // An updated minimum 20% fixed fee 8 | 9 | public double CustomFee 10 | { 11 | get { return _customFee; } 12 | set { _customFee = value; } 13 | } 14 | 15 | static void Main(string[] args) 16 | { 17 | Console.ReadKey(); 18 | } 19 | } 20 | 21 | class Scenario5Base 22 | { 23 | private double _minimumFee = 0.1; // A minimum 10% fixed fee (a design constraint for example) 24 | 25 | private double MinimumFee 26 | { 27 | get { return _minimumFee; } 28 | set { _minimumFee = value; } 29 | } 30 | 31 | /// 32 | /// Increment minimum fee by 1 percent 33 | /// 34 | /// 35 | /// Floating point arithmetic 36 | /// 37 | public void IncrementMinimumFee() 38 | { 39 | MinimumFee = (MinimumFee * 100 + 1) / 100; 40 | } 41 | 42 | public double GetPrice(double cost) 43 | { 44 | return cost * (1 + MinimumFee); 45 | } 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/Scenario6.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MethodRedirect 4 | { 5 | class Scenario6 6 | { 7 | static void Main(string[] args) 8 | { 9 | Console.ReadKey(); 10 | } 11 | 12 | public virtual string PublicVirtualInstanceMethod() 13 | { 14 | return "MethodRedirect.Scenario6.PublicVirtualInstanceMethod"; 15 | } 16 | 17 | public virtual string PublicVirtualInstanceMethodWithParameter(int x) 18 | { 19 | return "MethodRedirect.Scenario6.PublicVirtualInstanceMethodWithParameter." + x; 20 | } 21 | 22 | public virtual int AnotherPublicInstanceMethodWithParameter(int x) 23 | { 24 | return x + 1; 25 | } 26 | } 27 | 28 | class Scenario6Ext 29 | { 30 | private string PrivateInstanceMethodWithParameter(int x) 31 | { 32 | return "MethodRedirect.Scenario6Ext.PrivateInstanceMethodWithParameter." + x; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /UnitTests/MethodRedirect_UT.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {44352427-E1E0-4679-BBBD-0E298B2BAEB4} 8 | Library 9 | Properties 10 | MethodRedirect_UT 11 | MethodRedirect_UT 12 | v4.5 13 | 512 14 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 15.0 16 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 17 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 18 | False 19 | UnitTest 20 | 21 | 22 | 23 | 24 | true 25 | full 26 | false 27 | bin\Debug\ 28 | DEBUG;TRACE 29 | prompt 30 | 4 31 | 32 | 33 | pdbonly 34 | true 35 | bin\Release\ 36 | TRACE 37 | prompt 38 | 4 39 | 40 | 41 | true 42 | bin\x86\Debug\ 43 | DEBUG;TRACE 44 | full 45 | x86 46 | 7.3 47 | prompt 48 | 49 | 50 | bin\x86\Release\ 51 | TRACE 52 | true 53 | pdbonly 54 | x86 55 | 7.3 56 | prompt 57 | 58 | 59 | true 60 | bin\x64\Debug\ 61 | DEBUG;TRACE 62 | full 63 | x64 64 | 7.3 65 | prompt 66 | 67 | 68 | bin\x64\Release\ 69 | TRACE 70 | true 71 | pdbonly 72 | x64 73 | 7.3 74 | prompt 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 2.1.2 93 | 94 | 95 | 2.1.2 96 | 97 | 98 | 99 | 100 | {2a84dc12-471e-492f-b390-df3adb452e28} 101 | MethodRedirect 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /UnitTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("MethodRedirect_UT")] 6 | [assembly: AssemblyDescription("Unit tests for the MethodRedirect extension project.")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("NSPX Software")] 9 | [assembly: AssemblyProduct("MethodRedirect")] 10 | [assembly: AssemblyCopyright("Copyright © NSPX Software 2020")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("44352427-e1e0-4679-bbbd-0e298b2baeb4")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | -------------------------------------------------------------------------------- /UnitTests/Scenario1_UnitTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using MethodRedirect; 3 | using System; 4 | using System.Reflection; 5 | 6 | namespace Scenarios_UT 7 | { 8 | [TestClass] 9 | public class Scenario1_UnitTests 10 | { 11 | [TestMethod] 12 | public void Redirect_InternalInstanceMethod_To_PrivateInstanceMethod_SameInstance() 13 | { 14 | Assembly assembly = Assembly.GetAssembly(typeof(Scenario1)); 15 | Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario1"); 16 | 17 | MethodInfo Scenario_InternalInstanceMethod = Scenario_Type.GetMethod("InternalInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); 18 | MethodInfo Scenario_PrivateInstanceMethod = Scenario_Type.GetMethod("PrivateInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); 19 | 20 | var token = Scenario_InternalInstanceMethod.RedirectTo(Scenario_PrivateInstanceMethod); 21 | 22 | // Using "dynamic" type to prevent caching the first call result and make the second assert fail 23 | dynamic scenario = (Scenario1)Activator.CreateInstance(Scenario_Type); 24 | 25 | string methodName = scenario.InternalInstanceMethod(); 26 | 27 | Assert.IsTrue(methodName == "MethodRedirect.Scenario1.PrivateInstanceMethod"); 28 | 29 | token.Restore(); 30 | 31 | methodName = scenario.InternalInstanceMethod(); 32 | 33 | Assert.IsTrue(methodName == "MethodRedirect.Scenario1.InternalInstanceMethod"); 34 | } 35 | 36 | [TestMethod] 37 | public void Redirect_PublicInstanceMethod_To_PrivateInstanceMethod_SameInstance() 38 | { 39 | Assembly assembly = Assembly.GetAssembly(typeof(Scenario1)); 40 | Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario1"); 41 | 42 | MethodInfo Scenario_PublicInstanceMethod = Scenario_Type.GetMethod("PublicInstanceMethod", BindingFlags.Instance | BindingFlags.Public); 43 | MethodInfo Scenario_PrivateInstanceMethod = Scenario_Type.GetMethod("PrivateInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); 44 | 45 | var token = Scenario_PublicInstanceMethod.RedirectTo(Scenario_PrivateInstanceMethod); 46 | 47 | // Using "dynamic" type to prevent caching the first call result and make the second assert fail 48 | dynamic scenario = (Scenario1)Activator.CreateInstance(Scenario_Type); 49 | 50 | string methodName = scenario.PublicInstanceMethod(); 51 | 52 | Assert.IsTrue(methodName == "MethodRedirect.Scenario1.PrivateInstanceMethod"); 53 | 54 | token.Restore(); 55 | 56 | methodName = scenario.PublicInstanceMethod(); 57 | 58 | Assert.IsTrue(methodName == "MethodRedirect.Scenario1.PublicInstanceMethod"); 59 | } 60 | 61 | [TestMethod] 62 | public void Redirect_PublicInstanceMethod_To_PublicStaticMethod_SameInstance() 63 | { 64 | Assembly assembly = Assembly.GetAssembly(typeof(Scenario1)); 65 | Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario1"); 66 | 67 | MethodInfo Scenario_PublicInstanceMethod = Scenario_Type.GetMethod("PublicInstanceMethod", BindingFlags.Instance | BindingFlags.Public); 68 | MethodInfo Scenario_PublicStaticMethod = Scenario_Type.GetMethod("PublicStaticMethod", BindingFlags.Static | BindingFlags.Public); 69 | 70 | var token = Scenario_PublicInstanceMethod.RedirectTo(Scenario_PublicStaticMethod); 71 | 72 | // Using "dynamic" type to prevent caching the first call result and make the second assert fail 73 | dynamic scenario = (Scenario1)Activator.CreateInstance(Scenario_Type); 74 | 75 | string methodName = scenario.PublicInstanceMethod(); 76 | 77 | Assert.IsTrue(methodName == "MethodRedirect.Scenario1.PublicStaticMethod"); 78 | 79 | token.Restore(); 80 | 81 | methodName = scenario.PublicInstanceMethod(); 82 | 83 | Assert.IsTrue(methodName == "MethodRedirect.Scenario1.PublicInstanceMethod"); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /UnitTests/Scenario2_UnitTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using MethodRedirect; 3 | using System; 4 | using System.Reflection; 5 | 6 | namespace Scenarios_UT 7 | { 8 | [TestClass] 9 | public class Scenario2_UnitTests 10 | { 11 | [TestMethod] 12 | public void Redirect_InternalVirtualInstanceMethod_To_PrivateInstanceMethod_SameInstance() 13 | { 14 | Assembly assembly = Assembly.GetAssembly(typeof(Scenario2)); 15 | Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario2"); 16 | 17 | MethodInfo Scenario_InternalVirtualInstanceMethod = Scenario_Type.GetMethod("InternalVirtualInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); 18 | MethodInfo Scenario_PrivateInstanceMethod = Scenario_Type.GetMethod("PrivateInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); 19 | 20 | var token = Scenario_InternalVirtualInstanceMethod.RedirectTo(Scenario_PrivateInstanceMethod); 21 | 22 | var scenario = (Scenario2)Activator.CreateInstance(Scenario_Type); 23 | 24 | string methodName = scenario.InternalVirtualInstanceMethod(); 25 | 26 | Assert.IsTrue(methodName == "MethodRedirect.Scenario2.PrivateInstanceMethod"); 27 | 28 | token.Restore(); 29 | 30 | methodName = scenario.InternalVirtualInstanceMethod(); 31 | 32 | Assert.IsTrue(methodName == "MethodRedirect.Scenario2.InternalVirtualInstanceMethod"); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /UnitTests/Scenario3_UnitTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using MethodRedirect; 3 | using System; 4 | using System.Reflection; 5 | 6 | namespace Scenarios_UT 7 | { 8 | [TestClass] 9 | public class Scenario3_UnitTests 10 | { 11 | [TestMethod] 12 | public void Redirect_InternalVirtualInstanceMethod_To_InternalStaticMethod_DifferentInstance() 13 | { 14 | Assembly assembly = Assembly.GetAssembly(typeof(Scenario3)); 15 | Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario3"); 16 | Type ScenarioExt_Type = assembly.GetType("MethodRedirect.Scenario3Ext"); 17 | 18 | MethodInfo Scenario_InternalVirtualInstanceMethod = Scenario_Type.GetMethod("InternalVirtualInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); 19 | MethodInfo ScenarioExt_InternalStaticMethod = ScenarioExt_Type.GetMethod("InternalStaticMethod", BindingFlags.Static | BindingFlags.NonPublic); 20 | 21 | var token = Scenario_InternalVirtualInstanceMethod.RedirectTo(ScenarioExt_InternalStaticMethod); 22 | 23 | var scenario = (Scenario3)Activator.CreateInstance(Scenario_Type); 24 | 25 | string methodName = scenario.InternalVirtualInstanceMethod(); 26 | 27 | Assert.IsTrue(methodName == "MethodRedirect.Scenario3Ext.InternalStaticMethod"); 28 | 29 | token.Restore(); 30 | 31 | methodName = scenario.InternalVirtualInstanceMethod(); 32 | 33 | Assert.IsTrue(methodName == "MethodRedirect.Scenario3.InternalVirtualInstanceMethod"); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /UnitTests/Scenario4_UnitTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using MethodRedirect; 3 | using System; 4 | using System.Reflection; 5 | 6 | namespace Scenarios_UT 7 | { 8 | [TestClass] 9 | public class Scenario4_UnitTests 10 | { 11 | [TestMethod] 12 | public void Redirect_PublicVirtualInstanceMethod_To_PrivateInstanceMethod_DifferentInstance() 13 | { 14 | Assembly assembly = Assembly.GetAssembly(typeof(Scenario4)); 15 | Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario4"); 16 | Type ScenarioExt_Type = assembly.GetType("MethodRedirect.Scenario4Ext"); 17 | 18 | MethodInfo Scenario_PublicVirtualInstanceMethod = Scenario_Type.GetMethod("PublicVirtualInstanceMethod", BindingFlags.Instance | BindingFlags.Public); 19 | MethodInfo ScenarioExt_PrivateInstanceMethod = ScenarioExt_Type.GetMethod("PrivateInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic); 20 | 21 | var token = Scenario_PublicVirtualInstanceMethod.RedirectTo(ScenarioExt_PrivateInstanceMethod); 22 | 23 | var scenario = (Scenario4)Activator.CreateInstance(Scenario_Type); 24 | 25 | string methodName = scenario.PublicVirtualInstanceMethod(); 26 | 27 | Assert.IsTrue(methodName == "MethodRedirect.Scenario4Ext.PrivateInstanceMethod"); 28 | 29 | token.Restore(); 30 | 31 | methodName = scenario.PublicVirtualInstanceMethod(); 32 | 33 | Assert.IsTrue(methodName == "MethodRedirect.Scenario4.PublicVirtualInstanceMethod"); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /UnitTests/Scenario5_UnitTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using MethodRedirect; 3 | using System; 4 | using System.Reflection; 5 | 6 | namespace Scenarios_UT 7 | { 8 | [TestClass] 9 | public class Scenario5_UnitTests 10 | { 11 | [TestMethod] 12 | public void Redirect_PrivateAccessorMethods_To_PublicAccessorMethods_DerivedInstance() 13 | { 14 | Assembly assembly = Assembly.GetAssembly(typeof(Scenario5)); 15 | Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario5"); 16 | Type ScenarioBase_Type = assembly.GetType("MethodRedirect.Scenario5Base"); 17 | 18 | PropertyInfo Scenario_CustomFeeProperty = Scenario_Type.GetProperty("CustomFee", BindingFlags.Instance | BindingFlags.Public); 19 | PropertyInfo ScenarioBase_MinimumFeeProperty = ScenarioBase_Type.GetProperty("MinimumFee", BindingFlags.Instance | BindingFlags.NonPublic); 20 | 21 | // Obtain public accessor methods from main class 22 | MethodInfo Scenario_PublicGetCustomFeeMethod = Scenario_CustomFeeProperty.GetGetMethod(); 23 | MethodInfo Scenario_PublicSetCustomFeeMethod = Scenario_CustomFeeProperty.GetSetMethod(); 24 | 25 | // Obtain private accessor methods from base class 26 | MethodInfo ScenarioBase_PrivateGetMinimumFeeMethod = ScenarioBase_MinimumFeeProperty.GetGetMethod(true); // "true" for private accessor 27 | MethodInfo ScenarioBase_PrivateSetMinimumFeeMethod = ScenarioBase_MinimumFeeProperty.GetSetMethod(true); // "true" for private accessor 28 | 29 | FieldInfo ScenarioBase_PrivateMinimumFeeField = ScenarioBase_Type.GetField("_minimumFee", BindingFlags.Instance | BindingFlags.NonPublic); 30 | 31 | // Redirect the base class' private property accessors with the main's class public ones 32 | // Important: this must be done BEFORE creating an instance of the scenario otherwise the redirected addresses won't be set correctly. 33 | var tokenGet = ScenarioBase_PrivateGetMinimumFeeMethod.RedirectTo(Scenario_PublicGetCustomFeeMethod); 34 | var tokenSet = ScenarioBase_PrivateSetMinimumFeeMethod.RedirectTo(Scenario_PublicSetCustomFeeMethod); 35 | 36 | // Create instance of scenario 37 | var scenario = new Scenario5(); // (Scenario5)Activator.CreateInstance(Scenario_Type); 38 | 39 | // Test the "Get" accessor redirection 40 | double price = scenario.GetPrice(100); 41 | 42 | Assert.IsTrue(price == 120); // The price has a 20% fee added (instead of 10%) which is the custom value set on the main class 43 | 44 | // Test the "Set" accessor redirection 45 | scenario.IncrementMinimumFee(); 46 | 47 | Assert.IsTrue(scenario.CustomFee == 0.21); // The custom fee should now be 21% 48 | 49 | // Test "Get" accessor from base class 50 | double customFee = (double) ScenarioBase_PrivateGetMinimumFeeMethod.Invoke(scenario, null); 51 | 52 | Assert.IsTrue(customFee == 0.21); // The fee value should be the same value as the same redirected accessor was called 53 | 54 | // Another verification is to check the private member value of the base class 55 | double minimumFee = (double) ScenarioBase_PrivateMinimumFeeField.GetValue(scenario); 56 | 57 | Assert.IsTrue(minimumFee == 0.1); // Should be the default 10% field fee 58 | 59 | // Restoring accessors methods to original addresses 60 | tokenGet.Restore(); 61 | tokenSet.Restore(); 62 | 63 | // The base method addresses have been restored, for example a call to the private "Get" 64 | // accessor of the base class should return the original value 65 | minimumFee = (double) ScenarioBase_PrivateGetMinimumFeeMethod.Invoke(scenario, null); 66 | 67 | Assert.IsTrue(minimumFee == 0.1); // The 10% base class default value 68 | 69 | // **IMPORTANT** the restore operations have no effect on calls to 70 | // redirected methods made within already compiled methods. 71 | 72 | // For example, calling the GetPrice() method again that uses the private MinimumFee 73 | // property value to evaluate the result will not use the original value. 74 | double value = scenario.GetPrice(100); 75 | 76 | Assert.IsTrue(value == 121); // The GetPrice() method still uses the custom fee value of 21% set earlier 77 | 78 | // For the same reason stated above, the base class property "Set" accessor won't be 79 | // called within the IncrementMinimumFee() method. 80 | 81 | scenario.IncrementMinimumFee(); 82 | 83 | minimumFee = (double) ScenarioBase_MinimumFeeProperty.GetValue(scenario); 84 | 85 | Assert.IsTrue(minimumFee == 0.1); // The value of the base's class MinimumFee remains unchanged. 86 | 87 | // But, since the call used the redirected method, now the custom fee value of the main derived class has been updated. 88 | Assert.IsTrue(scenario.CustomFee == 0.22); 89 | 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /UnitTests/Scenario6_UnitTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using MethodRedirect; 3 | using System; 4 | using System.Reflection; 5 | using System.Diagnostics; 6 | 7 | namespace Scenarios_UT 8 | { 9 | [TestClass] 10 | public class Scenario6_UnitTests 11 | { 12 | [TestMethod] 13 | public void Redirect_PublicVirtualInstanceMethodWithParameter_To_PrivateInstanceMethodWithParameter_DifferentInstance() 14 | { 15 | Assembly assembly = Assembly.GetAssembly(typeof(Scenario6)); 16 | Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario6"); 17 | Type ScenarioExt_Type = assembly.GetType("MethodRedirect.Scenario6Ext"); 18 | 19 | MethodInfo Scenario_PublicVirtualInstanceMethodWithParameter = Scenario_Type.GetMethod("PublicVirtualInstanceMethodWithParameter", BindingFlags.Instance | BindingFlags.Public); 20 | MethodInfo ScenarioExt_PrivateInstanceMethodWithParameter = ScenarioExt_Type.GetMethod("PrivateInstanceMethodWithParameter", BindingFlags.Instance | BindingFlags.NonPublic); 21 | 22 | var token = Scenario_PublicVirtualInstanceMethodWithParameter.RedirectTo(ScenarioExt_PrivateInstanceMethodWithParameter); 23 | 24 | var scenario = (Scenario6)Activator.CreateInstance(Scenario_Type); 25 | 26 | int parameter = 123; 27 | string methodName = scenario.PublicVirtualInstanceMethodWithParameter(parameter); 28 | 29 | Assert.IsTrue(methodName == "MethodRedirect.Scenario6Ext.PrivateInstanceMethodWithParameter." + parameter); 30 | 31 | token.Restore(); 32 | 33 | methodName = scenario.PublicVirtualInstanceMethodWithParameter(parameter); 34 | 35 | Assert.IsTrue(methodName == "MethodRedirect.Scenario6.PublicVirtualInstanceMethodWithParameter." + parameter); 36 | } 37 | 38 | [TestMethod] 39 | public void Redirect_PublicVirtualInstanceMethod_To_Lambda_NoParameter() 40 | { 41 | Assembly assembly = Assembly.GetAssembly(typeof(Scenario6)); 42 | Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario6"); 43 | 44 | MethodInfo Scenario_PublicVirtualInstanceMethod = Scenario_Type.GetMethod("PublicVirtualInstanceMethod", BindingFlags.Instance | BindingFlags.Public); 45 | 46 | // Test redirection to lambda expression (no parameter) 47 | var token = Scenario_PublicVirtualInstanceMethod.RedirectTo(() => 48 | { 49 | return "MethodRedirect.LambdaExpression.NoParameter"; 50 | }); 51 | 52 | var scenario = (Scenario6)Activator.CreateInstance(Scenario_Type); 53 | 54 | string methodName = scenario.PublicVirtualInstanceMethod(); 55 | 56 | Assert.IsTrue(methodName == "MethodRedirect.LambdaExpression.NoParameter"); 57 | 58 | token.Restore(); 59 | 60 | methodName = scenario.PublicVirtualInstanceMethod(); 61 | 62 | Assert.IsTrue(methodName == "MethodRedirect.Scenario6.PublicVirtualInstanceMethod"); 63 | } 64 | 65 | [TestMethod] 66 | public void Redirect_PublicVirtualInstanceMethod_To_Lambda_WithParameter() 67 | { 68 | Assembly assembly = Assembly.GetAssembly(typeof(Scenario6)); 69 | Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario6"); 70 | 71 | MethodInfo Scenario_PublicVirtualInstanceMethodWithParameter = Scenario_Type.GetMethod("PublicVirtualInstanceMethodWithParameter", BindingFlags.Instance | BindingFlags.Public); 72 | 73 | // Test redirection to lambda expression with parameter (must use explicit type for parameter) 74 | var token = Scenario_PublicVirtualInstanceMethodWithParameter.RedirectTo((int x) => 75 | { 76 | return "MethodRedirect.LambdaExpression.WithParameter." + x; 77 | }); 78 | 79 | var scenario = (Scenario6)Activator.CreateInstance(Scenario_Type); 80 | 81 | int parameter = 456; 82 | string methodName = scenario.PublicVirtualInstanceMethodWithParameter(parameter); 83 | 84 | Assert.IsTrue(methodName == "MethodRedirect.LambdaExpression.WithParameter." + parameter); 85 | 86 | token.Restore(); 87 | 88 | methodName = scenario.PublicVirtualInstanceMethodWithParameter(parameter); 89 | 90 | Assert.IsTrue(methodName == "MethodRedirect.Scenario6.PublicVirtualInstanceMethodWithParameter." + parameter); 91 | } 92 | 93 | [TestMethod] 94 | public void Redirect_AnotherPublicInstanceMethod_To_Lambda_WithParameter() 95 | { 96 | Assembly assembly = Assembly.GetAssembly(typeof(Scenario6)); 97 | Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario6"); 98 | 99 | MethodInfo Scenario_AnotherPublicInstanceMethodWithParameter = Scenario_Type.GetMethod("AnotherPublicInstanceMethodWithParameter", BindingFlags.Instance | BindingFlags.Public); 100 | 101 | // Test redirection to lambda expression with parameter and integer return value (must use explicit type for parameter) 102 | var token = Scenario_AnotherPublicInstanceMethodWithParameter.RedirectTo((int x) => 103 | { 104 | Debug.WriteLine("Lambda Expression Parameter = " + x.ToString()); 105 | return x + 10; 106 | }); 107 | 108 | var scenario = (Scenario6)Activator.CreateInstance(Scenario_Type); 109 | 110 | int parameter = 1; 111 | int value = scenario.AnotherPublicInstanceMethodWithParameter(parameter); 112 | 113 | Assert.IsTrue(value == parameter + 10); 114 | 115 | token.Restore(); 116 | 117 | value = scenario.AnotherPublicInstanceMethodWithParameter(parameter); 118 | 119 | Assert.IsTrue(value == parameter + 1); 120 | } 121 | } 122 | } 123 | --------------------------------------------------------------------------------