Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
b8aff4e
#26 Change `thisType.IsValueType` to `constructor.IsValueType`
Miista May 2, 2024
211a5f5
#26 Add regression test for Miista/pose#26
Miista May 2, 2024
e388857
Update references to “Pose” with “Poser”
Miista May 2, 2024
afe5f08
Update Poser.nuspec
Miista May 2, 2024
6a440d0
Merge pull request #40 from Miista/26-enumisdefined-cannot-be-called-…
Miista May 2, 2024
eea75ec
#12 Add support for async methods
Miista May 2, 2024
bd96fa7
Fix last reference to “Pose”
Miista May 2, 2024
228cc2a
#36 Update badges in README
Miista May 2, 2024
570af2b
Merge pull request #41 from Miista/release/release-2.0.1
Miista May 2, 2024
da84ffd
#12 Add tests for shimming async methods
Miista May 2, 2024
f4904c7
Merge pull request #32 from Miista/28-update-readme-to-use-poser-nuge…
Miista May 2, 2024
d4ed536
Merge pull request #42 from Miista/36-update-badges-in-readme
Miista May 2, 2024
5a47ab3
#12 Successfully add support for async methods
Miista May 2, 2024
447b3ed
#12 Add net7.0 target to tests
Miista May 2, 2024
3ff8edb
#12 Clean up implementation
Miista May 2, 2024
b10d8b5
Merge remote-tracking branch 'origin/master' into 12-add-async-support
Miista May 2, 2024
9ed298f
#12 Bump version to 2.1.0-alpha0001
Miista May 2, 2024
95b9f35
#12: Add tests for async stubbing and getting the MoveNext method
Miista May 2, 2024
de427ae
#12 Add tests for more coverage
Miista May 2, 2024
599f7fc
Add more tests for coverage
Miista May 2, 2024
170a919
Remove redundant platform override #if
Miista May 2, 2024
1373e9d
Remove netcoreapp2.1 from test targets
Miista May 2, 2024
52e3c89
Remove netstandard2.1 from test target frameworks
Miista May 2, 2024
8db27e6
#12 Add examples to README
Miista May 2, 2024
3663b77
Swap usages of DEBUG with TRACE
Miista May 2, 2024
afcce01
#12 Begin adding tests for replacing async methods
Miista May 2, 2024
da34def
#12 Rearrange sections in README for clarity
Miista May 2, 2024
44e57ef
Merge remote-tracking branch 'origin/master' into 12-add-async-support
Miista May 2, 2024
cdf8430
#12 Emit leave instruction if rewriting an async method
Miista May 2, 2024
bef32fb
Update references to “Pose” with “Poser”
Miista Jan 16, 2025
a3df3d4
Fix last reference to “Pose”
Miista Jan 16, 2025
0e9881f
Merge pull request #32 from Miista/28-update-readme-to-use-poser-nuge…
Miista Jan 16, 2025
ddfb550
#26 Change `thisType.IsValueType` to `constructor.IsValueType`
Miista Jan 16, 2025
790b2ae
#26 Add regression test for Miista/pose#26
Miista Jan 16, 2025
d1a5a13
Update Poser.nuspec
Miista Jan 16, 2025
16abe83
Merge pull request #40 from Miista/26-enumisdefined-cannot-be-called-…
Miista Jan 16, 2025
297b8f1
#36 Update badges in README
Miista Jan 16, 2025
189685f
Merge pull request #41 from Miista/release/release-2.0.1
Miista Jan 16, 2025
9289b33
Merge pull request #42 from Miista/36-update-badges-in-readme
Miista Jan 16, 2025
e29b229
Merge remote-tracking branch 'refs/remotes/origin/master' into 12-add…
Miista Jan 16, 2025
0d2c6d7
#12: Add special case for rewriting AsyncMethodBuilderCore
Miista Jan 16, 2025
5874cc9
Something which works
Miista Jan 16, 2025
274d518
More stuff that nearly works
Miista Jan 16, 2025
697b39d
Successfully rewrite async method
Miista Jan 16, 2025
5b5908e
Add more code
Miista Jan 16, 2025
ac620c5
Add async support for method isolation
Miista Oct 28, 2025
6d04f46
Add net5.0 to target frameworks when testing
Miista Oct 28, 2025
3344aea
Merge remote-tracking branch 'origin/master' into 12-add-async-support
Miista Oct 28, 2025
b72a19c
Merge remote-tracking branch 'origin/master' into 12-add-async-support
Miista Oct 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,66 @@ Shimming of the following operators is not supported:
- `x >>> y` because expression trees cannot contain this operator. This is a limitation on the part of the compiler.
- `++` and `--` because these cannot be expressed in an expression tree.

## Async usage
### Shim static async method
```csharp
using Pose;

Shim staticTaskShim = Shim.Replace(() => DoWorkAsync()).With(
delegate
{
Console.Write("refusing to do work");
return Task.CompletedTask;
});
```

### Shim async instance method of a Reference Type
```csharp
using Pose;

Shim instanceTaskShim = Shim.Replace(() => Is.A<MyClass>().DoSomethingAsync()).With(
delegate(MyClass @this)
{
Console.WriteLine("doing something else async");
return Task.CompletedTask;
});
```

### Shim method of specific instance of a Reference Type
_Not supported for now. When supported, however, it will look like the following._

```csharp
using Pose;

MyClass myClass = new MyClass();
Shim myClassTaskShim = Shim.Replace(() => myClass.DoSomethingAsync()).With(
delegate(MyClass @this)
{
Console.WriteLine("doing something else with myClass async");
return Task.CompletedTask;
});
```

### Isolating your async code

```csharp
// This block executes immediately
await PoseContext.Isolate(async () =>
{
// All code that executes within this block
// is isolated and shimmed methods are replaced

// Outputs "refusing to do work"
await DoWorkAsync();

// Outputs "doing something else async"
new MyClass().DoSomethingAsync();

// Outputs "doing something else with myClass async"
await myClass.DoSomethingAsync();

}, staticTaskShim, instanceTaskShim, myClassTaskShim);
```
## Caveats & Limitations

* **Breakpoints** - At this time any breakpoints set anywhere in the isolated code and its execution path will not be hit. However, breakpoints set within a shim replacement delegate are hit.
Expand Down
64 changes: 64 additions & 0 deletions src/Pose/Extensions/TypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace Pose.Extensions
{
internal static class TypeExtensions
{
public static bool ImplementsInterface<TInterface>(this Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
if (!typeof(TInterface).IsInterface) throw new InvalidOperationException($"{typeof(TInterface)} is not an interface.");

return type.GetInterfaces().Any(interfaceType => interfaceType == typeof(TInterface));
}

public static bool HasAttribute<TAttribute>(this Type type) where TAttribute : Attribute
{
if (type == null) throw new ArgumentNullException(nameof(type));

var compilerGeneratedAttribute = type.GetCustomAttribute<TAttribute>() ?? type.ReflectedType?.GetCustomAttribute<TAttribute>();

return compilerGeneratedAttribute != null;
}

public static MethodInfo GetExplicitlyImplementedMethod<TInterface>(this Type type, string methodName)
{
if (type == null) throw new ArgumentNullException(nameof(type));
if (string.IsNullOrWhiteSpace(methodName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(methodName));

var interfaceType = type.GetInterfaceType<TInterface>() ?? throw new Exception();
var method = interfaceType.GetMethod(methodName) ?? throw new Exception();
var methodDeclaringType = method.DeclaringType ?? throw new Exception($"The {methodName} method does not have a declaring type");
var interfaceMapping = type.GetInterfaceMap(methodDeclaringType);
var requestedTargetMethod = interfaceMapping.TargetMethods.FirstOrDefault(m => m.Name == methodName);

return requestedTargetMethod;
}

private static Type GetInterfaceType<TInterface>(this Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
if (!typeof(TInterface).IsInterface) throw new InvalidOperationException($"{typeof(TInterface)} is not an interface.");

return type.GetInterfaces().FirstOrDefault(interfaceType => interfaceType == typeof(TInterface));
}

public static bool IsAsync(this Type thisType)
{
if (thisType == null) throw new ArgumentNullException(nameof(thisType));

return
// State machines are generated by the compiler...
thisType.HasAttribute<CompilerGeneratedAttribute>()

// as nested private classes...
&& thisType.IsNestedPrivate

// which implements IAsyncStateMachine.
&& thisType.ImplementsInterface<IAsyncStateMachine>();
}
}
}
11 changes: 6 additions & 5 deletions src/Pose/Helpers/StubHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

using System.Runtime.CompilerServices;
using Pose.Extensions;

namespace Pose.Helpers
Expand Down Expand Up @@ -58,6 +58,11 @@ public static MethodInfo DeVirtualizeMethod(Type thisType, MethodInfo virtualMet

var bindingFlags = BindingFlags.Instance | (virtualMethod.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic);
var types = virtualMethod.GetParameters().Select(p => p.ParameterType).ToArray();

if (thisType.IsAsync())
{
return thisType.GetExplicitlyImplementedMethod<IAsyncStateMachine>(nameof(IAsyncStateMachine.MoveNext));
}

return thisType.GetMethod(virtualMethod.Name, bindingFlags, null, types, null);
}
Expand Down Expand Up @@ -94,11 +99,7 @@ public static string CreateStubNameFromMethod(string prefix, MethodBase method)
if (genericArguments.Length > 0)
{
name += "[";
#if NETSTANDARD2_1_OR_GREATER
name += string.Join(',', genericArguments.Select(g => g.Name));
#else
name += string.Join(",", genericArguments.Select(g => g.Name));
#endif
name += "]";
}
}
Expand Down
Loading
Loading