You can use the trick shown below to create a method FriendRecieveMessageFromAlice
on Bob that can only be called by Alice
. An evil class, Eve
, won't be able to call that method without using reflection on private members.
I'm curious to know if this or another solution have been suggested before by other people. I've been looking for a solution to that problem for months, and I never saw one that ensured real friend
semantics provided that reflection isn't used (you can circumvent nearly anything with it).
Alice and Bob
public interface IKey { }
public class Alice
{
// Alice, Bob and Carol must only have private constructors, so only nested classes can subclass them.
private Alice() { }
public static Alice Create() { return new Alice(); }
private class AlicePrivateKey : Alice, IKey { }
public void PublicSendMessageToBob() {
Bob.Create().FriendRecieveMessageFromAlice<AlicePrivateKey>(42);
}
public void FriendRecieveMessageFromBob<TKey>(int message) where TKey : Bob, IKey {
System.Console.WriteLine("Alice: I recieved message {0} from my friend Bob.", message);
}
}
public class Bob
{
private Bob() { }
public static Bob Create() { return new Bob(); }
private class BobPrivateKey : Bob, IKey { }
public void PublicSendMessageToAlice() {
Alice.Create().FriendRecieveMessageFromBob<BobPrivateKey>(1337);
}
public void FriendRecieveMessageFromAlice<TKey>(int message) where TKey : Alice, IKey {
System.Console.WriteLine("Bob: I recieved message {0} from my friend Alice.", message);
}
}
class Program
{
static void Main(string[] args) {
Alice.Create().PublicSendMessageToBob();
Bob.Create().PublicSendMessageToAlice();
}
}
Eve
public class Eve
{
// Eve can't write that, it won't compile:
// 'Alice.Alice()' is inaccessible due to its protection level
private class EvePrivateKey : Alice, IKey { }
public void PublicSendMesssageToBob() {
// Eve can't write that either:
// 'Alice.AlicePrivateKey' is inaccessible due to its protection level
Bob.Create().FriendRecieveMessageFromAlice<Alice.AlicePrivateKey>(42);
}
}
How it works
The trick is that the method Bob.FriendRecieveMessageFromAlice
requires a (dummy) generic type parameter which serves as a token. That generic type must inherit from both Alice
, and from a dummy interface IKey
.
Since Alice
does not implement IKey
itself, the caller needs to provide some subclass of Alice
which does implement IKey
. However, Alice
only has private constructors, so it can only be subclassed by nested classes, and not by classes declared elsewhere.
This means that only a class nested in Alice
can subclass it to implement IKey
. That's what AlicePrivateKey
does, and since it is declared private, only Alice
can pass it as the generic argument to the Bob.FriendRecieveMessageFromAlice
, so only Alice
can call that method.
We then do the same thing the other way round so that only Bob
can call Alice.FriendRecieveMessageFromBob
.
Leaking the key
It is worth noting that, when called, Bob.FriendRecieveMessageFromAlice
has access to the TKey
generic type parameter, and could use it to spoof a call from Alice
on another method OtherClass.OtherMethod<OtherTkey>
accepting a OtherTKey : Alice, IKey
. It would therefore be safer to make the keys inherit from distinct interfaces: Alice, IBobKey
for the first, and Alice, IOtherKey
for the second.
Better than C++ friend
- Even
Bob
itself can't call its own method Bob.FriendRecieveMessageFromAlice
.
Bob can have multiple friends with distinct friend methods:
// Can only be called by Alice, not by Carol or Bob itself
Bob.FriendRecieveMessageFromAlice <TKey>(int message) where TKey : Alice, IKey { }
// Can only be called by Carol, not by Alice or Bob itself
Bob.FriendRecieveMessageFromCarol <TKey>(int message) where TKey : Carol, IKey { }
I'd be interested to know if there is some way to find tricks like this in a more efficient way than brute-force trial and error. Some kind of "algebra of C#'s type system", that tells us what restrictions can be enforced and what can't, but I haven't seen any discussion on that kind of topic.