Skip to content

Commit

Permalink
Support interface "inheritance"
Browse files Browse the repository at this point in the history
  • Loading branch information
menees committed Sep 14, 2022
1 parent 4646678 commit a824148
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

<!-- Make the assembly, file, and NuGet package versions the same. -->
<!-- https://docs.microsoft.com/en-us/nuget/concepts/package-versioning#pre-release-versions -->
<Version>0.6.0-beta</Version>
<Version>0.7.0-beta</Version>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)'=='Debug'">
Expand Down
37 changes: 34 additions & 3 deletions src/Menees.Remoting/RmiServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ public sealed class RmiServer<TServiceInterface> : RmiNode<TServiceInterface>, I
{
#region Private Data Members

private static readonly Dictionary<string, MethodInfo> MethodSignatureCache =
typeof(TServiceInterface).GetMethods().ToDictionary(method => GetMethodSignature(method));
private static readonly Lazy<Dictionary<string, MethodInfo>> MethodSignatureCache = new(CreateMethodSignatureDictionary);

private readonly PipeServer pipe;
private readonly CancellationToken cancellationToken;
Expand Down Expand Up @@ -134,6 +133,38 @@ protected override void Dispose(bool disposing)

#region Private Methods

private static Dictionary<string, MethodInfo> 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<string, MethodInfo> 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(
Expand All @@ -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."));
Expand Down
2 changes: 1 addition & 1 deletion tests/Menees.Remoting.Tests/BaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ protected async Task TestCrossProcessServerAsync(
}
finally
{
TimeSpan exitWait = TimeSpan.FromSeconds(5);
TimeSpan exitWait = TimeSpan.FromSeconds(10);
WaitForExit(hostProcess, exitWait, ExpectedExitCode);
}
}
Expand Down
56 changes: 56 additions & 0 deletions tests/Menees.Remoting.Tests/RmiServerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -143,6 +167,23 @@ public void SecurityVariations()
}
}

[TestMethod]
public void InterfaceInheritance()
{
string serverPath = this.GenerateServerPath();
Diamond diamond = new();
using RmiServer<IDiamond> server = new(diamond, serverPath, loggerFactory: this.LoggerFactory);
server.ReportUnhandledException = WriteUnhandledServerException;
server.Start();

using RmiClient<IDiamond> 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
Expand Down Expand Up @@ -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
}

0 comments on commit a824148

Please sign in to comment.