diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index efe0e87..008300f 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -29,7 +29,7 @@
- 0.6.0-beta
+ 0.7.0-beta
diff --git a/src/Menees.Remoting/RmiServer.cs b/src/Menees.Remoting/RmiServer.cs
index 67773a2..109241e 100644
--- a/src/Menees.Remoting/RmiServer.cs
+++ b/src/Menees.Remoting/RmiServer.cs
@@ -19,8 +19,7 @@ public sealed class RmiServer : RmiNode, I
{
#region Private Data Members
- private static readonly Dictionary MethodSignatureCache =
- typeof(TServiceInterface).GetMethods().ToDictionary(method => GetMethodSignature(method));
+ private static readonly Lazy> MethodSignatureCache = new(CreateMethodSignatureDictionary);
private readonly PipeServer pipe;
private readonly CancellationToken cancellationToken;
@@ -134,6 +133,38 @@ protected override void Dispose(bool disposing)
#region Private Methods
+ private static Dictionary CreateMethodSignatureDictionary()
+ {
+ List<(string Signature, MethodInfo Method)> items = new();
+
+ void AddMethods(Type interfaceType)
+ {
+ items.AddRange(interfaceType.GetMethods().Select(method => (GetMethodSignature(method), method)));
+
+ foreach (Type implementedType in interfaceType.GetInterfaces())
+ {
+ AddMethods(implementedType);
+ }
+ }
+
+ // Calling GetMethods() on an interface type only gets its declared methods. Even BindingFlags don't help
+ // because a "derived" interface still has "object" as its base and any "inherited" interface is really just an
+ // "implements" relationship and not an "inherits" relationship. So, we have to recursively find all "inherited"
+ // interfaces and flatten them out ourselves.
+ // https://stackoverflow.com/questions/3395174/c-sharp-interface-inheritance/3395327#3395327
+ AddMethods(typeof(TServiceInterface));
+
+ // Take the first MethodInfo instance for each signature because interfaces can have a diamond shaped
+ // "inheritance" pattern. For example, suppose an interface "inherits" from two interfaces that are
+ // both IDisposable like this:
+ // IStreamReader : IDisposable
+ // IStreamWriter : IDisposable
+ // IStreamReaderWriter : IStreamReader, IStreamWriter
+ Dictionary result = items.GroupBy(pair => pair.Signature)
+ .ToDictionary(pair => pair.Key, pair => pair.First().Method);
+ return result;
+ }
+
private async Task ProcessRequestAsync(Stream clientStream)
{
await ServerUtility.ProcessRequestAsync(
@@ -144,7 +175,7 @@ await ServerUtility.ProcessRequestAsync(
{
Response response;
- if (!MethodSignatureCache.TryGetValue(request.MethodSignature ?? string.Empty, out MethodInfo? method))
+ if (!MethodSignatureCache.Value.TryGetValue(request.MethodSignature ?? string.Empty, out MethodInfo? method))
{
response = ServerUtility.CreateResponse(new TargetException(
$"A {typeof(TServiceInterface).FullName} method with signature '{request.MethodSignature}' was not found."));
diff --git a/tests/Menees.Remoting.Tests/BaseTests.cs b/tests/Menees.Remoting.Tests/BaseTests.cs
index 3377d33..db8fc08 100644
--- a/tests/Menees.Remoting.Tests/BaseTests.cs
+++ b/tests/Menees.Remoting.Tests/BaseTests.cs
@@ -122,7 +122,7 @@ protected async Task TestCrossProcessServerAsync(
}
finally
{
- TimeSpan exitWait = TimeSpan.FromSeconds(5);
+ TimeSpan exitWait = TimeSpan.FromSeconds(10);
WaitForExit(hostProcess, exitWait, ExpectedExitCode);
}
}
diff --git a/tests/Menees.Remoting.Tests/RmiServerTests.cs b/tests/Menees.Remoting.Tests/RmiServerTests.cs
index 23ce6f7..71fc4d7 100644
--- a/tests/Menees.Remoting.Tests/RmiServerTests.cs
+++ b/tests/Menees.Remoting.Tests/RmiServerTests.cs
@@ -14,6 +14,30 @@
[TestClass]
public class RmiServerTests : BaseTests
{
+ #region Private Interfaces
+
+ private interface IRoot
+ {
+ int GetRoot(int input);
+ }
+
+ private interface ILevel1A : IRoot
+ {
+ int GetLevel1A(int input);
+ }
+
+ private interface ILevel1B : IRoot
+ {
+ int GetLevel1B(int input);
+ }
+
+ private interface IDiamond : ILevel1A, ILevel1B
+ {
+ int GetDiamond(int input);
+ }
+
+ #endregion
+
#region Public Methods
[TestMethod]
@@ -143,6 +167,23 @@ public void SecurityVariations()
}
}
+ [TestMethod]
+ public void InterfaceInheritance()
+ {
+ string serverPath = this.GenerateServerPath();
+ Diamond diamond = new();
+ using RmiServer server = new(diamond, serverPath, loggerFactory: this.LoggerFactory);
+ server.ReportUnhandledException = WriteUnhandledServerException;
+ server.Start();
+
+ using RmiClient client = new(serverPath, loggerFactory: this.LoggerFactory);
+ IDiamond proxy = client.CreateProxy();
+ proxy.GetRoot(10).ShouldBe(10);
+ proxy.GetLevel1A(10).ShouldBe(110);
+ proxy.GetLevel1B(10).ShouldBe(210);
+ proxy.GetDiamond(10).ShouldBe(1010);
+ }
+
#endregion
#region Private Methods
@@ -206,4 +247,19 @@ private void TestClient(int clientCount, string serverPath, ClientSecurity? clie
}
#endregion
+
+ #region Private Types
+
+ private sealed class Diamond : IDiamond
+ {
+ public int GetDiamond(int input) => input + 1000;
+
+ public int GetLevel1A(int input) => input + 100;
+
+ public int GetLevel1B(int input) => input + 200;
+
+ public int GetRoot(int input) => input;
+ }
+
+ #endregion
}