Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Enhancements:
- DynamicProxy supported C# `in` parameter modifiers only on the .NET Framework up until now. Adding .NET Standard 1.5 as an additional target to the NuGet package makes them work on .NET Core, too (@stakx, #339)
- Replicate custom attributes on constructor parameters in the generated proxy type constructors to fulfill introspection of constructors. This does not change the proxying behavior. (@stakx, #341)
- Improve performance of InvocationHelper cache lookups (@tangdf, #358)
- Improve fidelity of default value replication of optional parameters to fulfill inspection of the generated proxies. This does not change the proxying behavior. (@stakx, #356)

Bugfixes:
- Fix Castle.Services.Logging.Log4netIntegration assembly file name casing which breaks on Linux (@beginor, #324)
Expand Down
43 changes: 43 additions & 0 deletions docs/dynamicproxy-optional-parameter-value-limitations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Optional parameter value limitations

DynamicProxy uses `System.Reflection` and `System.Reflection.Emit` to create proxy types. Due to several bugs and limitations in these facilities (both on .NET and Mono), it is not possible in every single case to faithfully reproduce default values of optional parameters in the dynamically generated proxy types.

This usually doesn't matter much in practice, but it may become a problem for frameworks or libraries (such as Dependency Injection containers) that reflect over the generated proxy types.


## When your code runs on Mono

On Mono (up to and including at least version 5.10.1.47), DynamicProxy may not be able to correctly reproduce default parameter values in the proxy type for...

* **Optional parameters of any nullable type `Nullable<T>`.** Reflection will likely report (via `ParameterInfo.[Raw]DefaultValue`) a default value of `null` for such parameters, regardless of whether the actual default value in the proxied method was `null` or not.

Therefore, you cannot trust the reported default value, *unless* you know you're running on a version of Mono where the underlying issues have been resolved, or when you've double-checked the default value in the original method of the proxied type.

The underlying causes have been documented in [mono/mono#8504](https://github.com/mono/mono/issues/8504) and [mono/mono#8597](https://github.com/mono/mono/issues/8597).


## When your code runs on the .NET Framework or on .NET Core

The .NET Framework (up to and including at least version 4.7.1) and .NET Core (up to and including at least version 2.1) are affected by several bugs or limitations regarding default parameter values. DynamicProxy may not be able to correctly reproduce default parameter values in the proxy type for...

* **Optional parameters of any nullable type `Nullable<T>`.** On the .NET Framework 3.5 only, reflection will likely report (via `ParameterInfo.[Raw]DefaultValue`) a default value of `Missing.Value` for such parameters.

There is no easy way to quickly guess what the correct default value might be. Consider upgrading to the .NET Framework 4 or later, or double-check the default value in the original method of the proxied type.

* **Optional parameters of some `struct` type `SomeStruct` having a default value of `default(SomeStruct)`.** If reflection reports (via `ParameterInfo.[Raw]DefaultValue`) a default value of `Missing.Value` for such parameters, you may safely assume that the *correct* default value is `default(SomeStruct)`.

Note that if reflection reports a default value of `null` in such cases, this is not an error, but normal `System.Reflection` behavior that is to be expected. In this case, you may also safely assume `default(SomeStruct)` to be the correct default value.

For .NET Core, the underlying cause of this problem has been documented in [dotnet/corefx#26164](https://github.com/dotnet/corefx/issues/26164).

* **Optional parameters of some nullable `enum` type `SomeEnum?` having a non-`null` default value.** If reflection reports (via `ParameterInfo.[Raw]DefaultValue`) a default value of `Missing.Value` for such parameters, the only thing you may safely assume is that the actual default value is not `null`.

You can only find out the correct default value by double-checking the default value in the original method of the proxied type.

For .NET Core, the underlying cause of this problem has been documented in [dotnet/coreclr#17893](https://github.com/dotnet/coreclr/issues/17893).

* **Optional parameters of a generic parameter type instantiated as some `enum` type `SomeEnum`.** For example, given a generic type `C<T>` and a method `void M(T arg = default(T))`, if you proxy the closed generic type `C<SomeEnum>`, reflection might then report (via `ParameterInfo.[Raw]DefaultValue`) a default value of `Missing.Value` for the proxied `arg` parameter. If so, you may safely assume that the actual default value is `default(SomeEnum)`.

Note that if reflection reports a default value of `null` in such cases, this is not an error, but normal `System.Reflection` behavior that is to be expected. In this case, you may also safely assume `default(SomeEnum)` to be the correct default value.

For .NET Core, the underlying cause of this problem has been documented in [dotnet/coreclr#29570](https://github.com/dotnet/corefx/issues/29570).
1 change: 1 addition & 0 deletions docs/dynamicproxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ If you're new to DynamicProxy you can read a [quick introduction](dynamicproxy-i
* [Use proxy generation hooks and interceptor selectors for fine grained control](dynamicproxy-fine-grained-control.md)
* [SRP applies to interceptors](dynamicproxy-srp-applies-to-interceptors.md)
* [Behavior of by-reference parameters during interception](dynamicproxy-by-ref-parameters.md)
* [Optional parameter value limitations](dynamicproxy-optional-parameter-value-limitations.md)

:information_source: **Where is `Castle.DynamicProxy.dll`?:** DynamicProxy used to live in its own assembly. As part of changes in version 2.5 it was merged into `Castle.Core.dll` and that's where you'll find it.

Expand Down
6 changes: 3 additions & 3 deletions src/Castle.Core.Tests/Core.Tests/Logging/TraceLoggerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public void Cleanup()

#if FEATURE_SYSTEM_CONFIGURATION
[Test]
[ExcludeOnMono("Mono has a bug that causes the listeners to not fully work.")]
[ExcludeOnFramework(Framework.Mono, "Mono has a bug that causes the listeners to not fully work.")]
public void WritingToLoggerByType()
{
TraceLoggerFactory factory = new TraceLoggerFactory();
Expand Down Expand Up @@ -85,7 +85,7 @@ public void TracingErrorInformation()
}

[Test]
[ExcludeOnMono("Mono has a bug that causes the listeners to not fully work.")]
[ExcludeOnFramework(Framework.Mono, "Mono has a bug that causes the listeners to not fully work.")]
public void FallUpToShorterSourceName()
{
TraceLoggerFactory factory = new TraceLoggerFactory();
Expand All @@ -97,7 +97,7 @@ public void FallUpToShorterSourceName()
}

[Test]
[ExcludeOnMono("Mono has a bug that causes the listeners to not fully work.")]
[ExcludeOnFramework(Framework.Mono, "Mono has a bug that causes the listeners to not fully work.")]
public void FallUpToDefaultSource()
{
TraceLoggerFactory factory = new TraceLoggerFactory();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ private void FindVerificationErrors()

#if FEATURE_ASSEMBLYBUILDER_SAVE
[Test]
[ExcludeOnMono("Mono doesn't have peverify, so we can't perform verification.")]
[ExcludeOnFramework(Framework.Mono, "Mono doesn't have peverify, so we can't perform verification.")]
public void TearDown_FindsVerificationErrors()
{
var ex = Assert.Throws<AssertionException>(() => FindVerificationErrors());
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public void EnsureProxyHasAttributesOnParameter()
}

[Test]
[ExcludeOnMono("Mono does not currently emit custom attributes on generic type parameters of methods. See https://github.com/mono/mono/issues/8512.")]
[ExcludeOnFramework(Framework.Mono, "Mono does not currently emit custom attributes on generic type parameters of methods. See https://github.com/mono/mono/issues/8512.")]
public void EnsureProxyHasAttributesOnGenericArgument()
{
var proxy = generator.CreateClassProxy<HasNonInheritableAttribute>();
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public void ModuleScopeCanHandleSignedAndUnsignedInParallel()

#if FEATURE_ASSEMBLYBUILDER_SAVE
[Test]
[ExcludeOnMono("On Mono, `ModuleBuilder.FullyQualifiedName` does not return a fully qualified name including a path. See https://github.com/mono/mono/issues/8503.")]
[ExcludeOnFramework(Framework.Mono, "On Mono, `ModuleBuilder.FullyQualifiedName` does not return a fully qualified name including a path. See https://github.com/mono/mono/issues/8503.")]
public void ImplicitModulePaths()
{
var scope = new ModuleScope(true);
Expand All @@ -87,7 +87,7 @@ public void ImplicitModulePaths()
}

[Test]
[ExcludeOnMono("On Mono, `ModuleBuilder.FullyQualifiedName` does not return a fully qualified name including a path. See https://github.com/mono/mono/issues/8503.")]
[ExcludeOnFramework(Framework.Mono, "On Mono, `ModuleBuilder.FullyQualifiedName` does not return a fully qualified name including a path. See https://github.com/mono/mono/issues/8503.")]
public void ExplicitModulePaths()
{
var scope = new ModuleScope(true, false, "Strong", "StrongModule.dll", "Weak", "WeakModule.dll");
Expand Down Expand Up @@ -130,7 +130,7 @@ private static void CheckSignedSavedAssembly(string path)
}

[Test]
[ExcludeOnMono("On Mono, `ModuleBuilder.FullyQualifiedName` does not return a fully qualified name including a path. See https://github.com/mono/mono/issues/8503.")]
[ExcludeOnFramework(Framework.Mono, "On Mono, `ModuleBuilder.FullyQualifiedName` does not return a fully qualified name including a path. See https://github.com/mono/mono/issues/8503.")]
public void SaveSigned()
{
var scope = new ModuleScope(true);
Expand All @@ -152,7 +152,7 @@ public void SaveSigned()

#if FEATURE_ASSEMBLYBUILDER_SAVE
[Test]
[ExcludeOnMono("On Mono, `ModuleBuilder.FullyQualifiedName` does not return a fully qualified name including a path. See https://github.com/mono/mono/issues/8503.")]
[ExcludeOnFramework(Framework.Mono, "On Mono, `ModuleBuilder.FullyQualifiedName` does not return a fully qualified name including a path. See https://github.com/mono/mono/issues/8503.")]
public void SaveUnsigned()
{
var scope = new ModuleScope(true);
Expand Down
Loading