diff --git a/src/NSubstitute/Core/IProxyFactory.cs b/src/NSubstitute/Core/IProxyFactory.cs
index 541aad90..31cd3ed8 100644
--- a/src/NSubstitute/Core/IProxyFactory.cs
+++ b/src/NSubstitute/Core/IProxyFactory.cs
@@ -2,5 +2,5 @@ namespace NSubstitute.Core;
public interface IProxyFactory
{
- object GenerateProxy(ICallRouter callRouter, Type typeToProxy, Type[]? additionalInterfaces, object?[]? constructorArguments);
+ object GenerateProxy(ICallRouter callRouter, Type typeToProxy, Type[]? additionalInterfaces, bool isPartial, object?[]? constructorArguments);
}
\ No newline at end of file
diff --git a/src/NSubstitute/Core/SubstituteFactory.cs b/src/NSubstitute/Core/SubstituteFactory.cs
index 76a9f652..e55c2ffd 100644
--- a/src/NSubstitute/Core/SubstituteFactory.cs
+++ b/src/NSubstitute/Core/SubstituteFactory.cs
@@ -14,7 +14,7 @@ public class SubstituteFactory(ISubstituteStateFactory substituteStateFactory, I
///
public object Create(Type[] typesToProxy, object?[] constructorArguments)
{
- return Create(typesToProxy, constructorArguments, callBaseByDefault: false);
+ return Create(typesToProxy, constructorArguments, callBaseByDefault: false, isPartial: false);
}
///
@@ -33,10 +33,10 @@ public object CreatePartial(Type[] typesToProxy, object?[] constructorArguments)
throw new CanNotPartiallySubForInterfaceOrDelegateException(primaryProxyType);
}
- return Create(typesToProxy, constructorArguments, callBaseByDefault: true);
+ return Create(typesToProxy, constructorArguments, callBaseByDefault: true, isPartial: true);
}
- private object Create(Type[] typesToProxy, object?[] constructorArguments, bool callBaseByDefault)
+ private object Create(Type[] typesToProxy, object?[] constructorArguments, bool callBaseByDefault, bool isPartial)
{
var substituteState = substituteStateFactory.Create(this);
substituteState.CallBaseConfiguration.CallBaseByDefault = callBaseByDefault;
@@ -46,7 +46,7 @@ private object Create(Type[] typesToProxy, object?[] constructorArguments, bool
var callRouter = callRouterFactory.Create(substituteState, canConfigureBaseCalls);
var additionalTypes = typesToProxy.Where(x => x != primaryProxyType).ToArray();
- var proxy = proxyFactory.GenerateProxy(callRouter, primaryProxyType, additionalTypes, constructorArguments);
+ var proxy = proxyFactory.GenerateProxy(callRouter, primaryProxyType, additionalTypes, isPartial, constructorArguments);
return proxy;
}
diff --git a/src/NSubstitute/Exceptions/TypeForwardingException.cs b/src/NSubstitute/Exceptions/TypeForwardingException.cs
new file mode 100644
index 00000000..5ddf2703
--- /dev/null
+++ b/src/NSubstitute/Exceptions/TypeForwardingException.cs
@@ -0,0 +1,21 @@
+namespace NSubstitute.Exceptions;
+
+public abstract class TypeForwardingException(string message) : SubstituteException(message)
+{
+}
+
+public sealed class CanNotForwardCallsToClassNotImplementingInterfaceException(Type type) : TypeForwardingException(DescribeProblem(type))
+{
+ private static string DescribeProblem(Type type)
+ {
+ return string.Format("The provided class '{0}' doesn't implement all requested interfaces. ", type.Name);
+ }
+}
+
+public sealed class CanNotForwardCallsToAbstractClassException(Type type) : TypeForwardingException(DescribeProblem(type))
+{
+ private static string DescribeProblem(Type type)
+ {
+ return string.Format("The provided class '{0}' is abstract. ", type.Name);
+ }
+}
diff --git a/src/NSubstitute/Proxies/CastleDynamicProxy/CastleDynamicProxyFactory.cs b/src/NSubstitute/Proxies/CastleDynamicProxy/CastleDynamicProxyFactory.cs
index e1c0d1ef..a445f42f 100644
--- a/src/NSubstitute/Proxies/CastleDynamicProxy/CastleDynamicProxyFactory.cs
+++ b/src/NSubstitute/Proxies/CastleDynamicProxy/CastleDynamicProxyFactory.cs
@@ -10,14 +10,14 @@ public class CastleDynamicProxyFactory(ICallFactory callFactory, IArgumentSpecif
private readonly ProxyGenerator _proxyGenerator = new ProxyGenerator();
private readonly AllMethodsExceptCallRouterCallsHook _allMethodsExceptCallRouterCallsHook = new AllMethodsExceptCallRouterCallsHook();
- public object GenerateProxy(ICallRouter callRouter, Type typeToProxy, Type[]? additionalInterfaces, object?[]? constructorArguments)
+ public object GenerateProxy(ICallRouter callRouter, Type typeToProxy, Type[]? additionalInterfaces, bool isPartial, object?[]? constructorArguments)
{
return typeToProxy.IsDelegate()
? GenerateDelegateProxy(callRouter, typeToProxy, additionalInterfaces, constructorArguments)
- : GenerateTypeProxy(callRouter, typeToProxy, additionalInterfaces, constructorArguments);
+ : GenerateTypeProxy(callRouter, typeToProxy, additionalInterfaces, isPartial, constructorArguments);
}
- private object GenerateTypeProxy(ICallRouter callRouter, Type typeToProxy, Type[]? additionalInterfaces, object?[]? constructorArguments)
+ private object GenerateTypeProxy(ICallRouter callRouter, Type typeToProxy, Type[]? additionalInterfaces, bool isPartial, object?[]? constructorArguments)
{
VerifyClassHasNotBeenPassedAsAnAdditionalInterface(additionalInterfaces);
@@ -31,7 +31,8 @@ private object GenerateTypeProxy(ICallRouter callRouter, Type typeToProxy, Type[
additionalInterfaces,
constructorArguments,
[proxyIdInterceptor, forwardingInterceptor],
- proxyGenerationOptions);
+ proxyGenerationOptions,
+ isPartial);
forwardingInterceptor.SwitchToFullDispatchMode();
return proxy;
@@ -54,7 +55,8 @@ private object GenerateDelegateProxy(ICallRouter callRouter, Type delegateType,
additionalInterfaces: null,
constructorArguments: null,
interceptors: [proxyIdInterceptor, forwardingInterceptor],
- proxyGenerationOptions);
+ proxyGenerationOptions,
+ isPartial: false);
forwardingInterceptor.SwitchToFullDispatchMode();
@@ -75,8 +77,13 @@ private CastleForwardingInterceptor CreateForwardingInterceptor(ICallRouter call
private object CreateProxyUsingCastleProxyGenerator(Type typeToProxy, Type[]? additionalInterfaces,
object?[]? constructorArguments,
IInterceptor[] interceptors,
- ProxyGenerationOptions proxyGenerationOptions)
+ ProxyGenerationOptions proxyGenerationOptions,
+ bool isPartial)
{
+ if (isPartial)
+ return CreatePartialProxy(typeToProxy, additionalInterfaces, constructorArguments, interceptors, proxyGenerationOptions, isPartial);
+
+
if (typeToProxy.GetTypeInfo().IsInterface)
{
VerifyNoConstructorArgumentsGivenForInterface(constructorArguments);
@@ -96,6 +103,7 @@ private object CreateProxyUsingCastleProxyGenerator(Type typeToProxy, Type[]? ad
additionalInterfaces = interfaces;
}
+
return _proxyGenerator.CreateClassProxy(typeToProxy,
additionalInterfaces,
proxyGenerationOptions,
@@ -103,6 +111,32 @@ private object CreateProxyUsingCastleProxyGenerator(Type typeToProxy, Type[]? ad
interceptors);
}
+ private object CreatePartialProxy(Type typeToProxy, Type[]? additionalInterfaces, object?[]? constructorArguments, IInterceptor[] interceptors, ProxyGenerationOptions proxyGenerationOptions, bool isPartial)
+ {
+ if (typeToProxy.GetTypeInfo().IsClass &&
+ additionalInterfaces != null &&
+ additionalInterfaces.Any())
+ {
+ VerifyClassIsNotAbstract(typeToProxy);
+ VerifyClassImplementsAllInterfaces(typeToProxy, additionalInterfaces);
+
+ var targetObject = Activator.CreateInstance(typeToProxy, constructorArguments);
+ typeToProxy = additionalInterfaces.First();
+
+ return _proxyGenerator.CreateInterfaceProxyWithTarget(typeToProxy,
+ additionalInterfaces,
+ target: targetObject,
+ options: proxyGenerationOptions,
+ interceptors: interceptors);
+ }
+
+ return _proxyGenerator.CreateClassProxy(typeToProxy,
+ additionalInterfaces,
+ proxyGenerationOptions,
+ constructorArguments,
+ interceptors);
+ }
+
private ProxyGenerationOptions GetOptionsToMixinCallRouterProvider(ICallRouter callRouter)
{
var options = new ProxyGenerationOptions(_allMethodsExceptCallRouterCallsHook);
@@ -116,6 +150,22 @@ private ProxyGenerationOptions GetOptionsToMixinCallRouterProvider(ICallRouter c
return options;
}
+ private static void VerifyClassImplementsAllInterfaces(Type classType, IEnumerable additionalInterfaces)
+ {
+ if (!additionalInterfaces.All(x => x.GetTypeInfo().IsAssignableFrom(classType.GetTypeInfo())))
+ {
+ throw new CanNotForwardCallsToClassNotImplementingInterfaceException(classType);
+ }
+ }
+
+ private static void VerifyClassIsNotAbstract(Type classType)
+ {
+ if (classType.GetTypeInfo().IsAbstract)
+ {
+ throw new CanNotForwardCallsToAbstractClassException(classType);
+ }
+ }
+
private static void VerifyNoConstructorArgumentsGivenForInterface(object?[]? constructorArguments)
{
if (HasItems(constructorArguments))
diff --git a/src/NSubstitute/Proxies/CastleDynamicProxy/CastleInvocationMapper.cs b/src/NSubstitute/Proxies/CastleDynamicProxy/CastleInvocationMapper.cs
index 7ebac3e9..ddc15405 100644
--- a/src/NSubstitute/Proxies/CastleDynamicProxy/CastleInvocationMapper.cs
+++ b/src/NSubstitute/Proxies/CastleDynamicProxy/CastleInvocationMapper.cs
@@ -10,8 +10,7 @@ public virtual ICall Map(IInvocation castleInvocation)
Func