views:

181

answers:

3

Since, two methods with the same parameters but different return values will not compile. What is the best way to define this interface without loosing clarity?

public interface IDuplexChannel<T, U>
{
    void Send(T value, int timeout = -1);
    void Send(U value, int timeout = -1);
    bool TrySend(T value, int timeout = -1);
    bool TrySend(U value, int timeout = -1);
    T Receive(int timeout = -1);
    U Receive(int timeout = -1);
    bool TryReceive(out T value, int timeout = -1);
    bool TryReceive(out U value, int timeout = -1);
}

I considered using out params but that would make it a little awkward to use.

public interface IDuplexChannel<T, U>
{
    void Send(T value, int timeout = -1);
    void Send(U value, int timeout = -1);
    bool TrySend(T value, int timeout = -1);
    bool TrySend(U value, int timeout = -1);
    void Receive(out T value, int timeout = -1);
    void Receive(out U value, int timeout = -1);
    bool TryReceive(out T value, int timeout = -1);
    bool TryReceive(out U value, int timeout = -1);
}

Generic version, a little unwieldy but it works.

public interface IDuplexChannel<T, U>
{
    void Send(T value, int timeout = -1);
    void Send(U value, int timeout = -1);
    bool TrySend(T value, int timeout = -1);
    bool TrySend(U value, int timeout = -1);
    V Receive<V>(int timeout = -1) where V : T, U;
    bool TryReceive(out T value, int timeout = -1);
    bool TryReceive(out U value, int timeout = -1);
}
+4  A: 

Rename these two methods. They only differ by return type.

T Receive(int timeout = -1);
U Receive(int timeout = -1);

Note, I have not tested this. Try this.

R Receive<R>(int timeout = -1);
Daniel A. White
Any name suggestions?
ChaosPandion
Your second suggestion is my fallback. The reason I don't like it is that you have to specifically define the type `Receive<MyLongClassName>`.
ChaosPandion
If you want my 2 cents... I say get rid of the Receive methods and just use the TryReceive. Whenever given the choice of a Something or TrySomething method, I always prefer the latter.
Josh Einstein
@Josh - I've considered doing that, however I decided to add the non-try methods so you wouldn't have to raise the exception yourself if you have no way to handle the failure.
ChaosPandion
I would say keep both the Try and non-try versions. Those two have different calling semantics if you follow the standard .NET patterns. I would also avoid having both Receive and TryReceive use out parameters...that kind of defeats the purpose and value of having both.
jrista
Fair enough. But I'd say that given the fact that TryReceive will probably be more commonly used you may not need to worry so much about the Receive<T>(...) syntax.
Josh Einstein
Why would you say that TryReceive will be more commonly used? Unless I need to explicitly handle the case where a receive fails from the same location I call it, I would generally use Receive and let exceptions bubble. You can argue for both cases, which is why both cases tend to exist in most Microsoft interfaces. Ultimately, its a context-dependent decision which one you use, and both have value.
jrista
@Daniel A. White is right. When you have two separate methods with the same signature, you can make one storngly-typed with a Type parameter, so it will be accepted as one is typed, and the other is not. The <T> is a parameter, so the signature is no longer the same, and no need to rename your method at your convenience. =)
Will Marcouiller
+2  A: 

The problem is indeed that there are two Receive methods that differ only by the return type. Because your type represents a duplex channel, you had to duplicate everything in the interface - I believe a simpler approach would be to define a type that allows you to represent "either T or U" value. This is quite related to Tuple<T, U>, which is now in .NET that allows you to represent "both T and U". The signature of the type could look like this:

// Represents either a value of type T or a value of type U
class Either<T, U> { 
  public bool TryGetFirst(out T val);
  public bool TryGetSecond(out U val);
}

// For constructing values of type Either<T, U>
static class Either {
  public static Either<T, U> First<T, U>(T val);
  public static Either<T, U> Second<T, U>(U val);
}

A sample usage of this class might look like this:

var val = Either.First<int, string>(42);

int num;
string str;
if (val.TryGetFirst(out num)) 
  Console.WriteLine("Got number: {0}", num);
else if (val.TryGetSecond(out str)) 
  Console.WriteLine("Got string: {0}", str);

Then you can represent your duplex channel using a simpler interface:

public interface IDuplexChannel<T, U> { 
    void Send(Either<T, U> value, int timeout = -1); 
    bool TrySend(Either<T, U> value, int timeout = -1); 
    Either<T, U> Receive(int timeout = -1); 
    bool TryReceive(out Either<T, U> value, int timeout = -1); 
} 

As Josh suggest, I would also get rid of the Receive and Send methods. Why? Because it makes implementing the interface simple and you can easily provide an implementation of Receive and Send in terms of TryReceive and TrySend as an extension method. So, you end up with an interface:

public interface IDuplexChannel<T, U> { 
    bool TrySend(Either<T, U> value, int timeout = -1); 
    bool TryReceive(out Either<T, U> value, int timeout = -1); 
} 

And the extension methods would look roughly like this:

public static Either<T, U> Receive
    (this IDuplexChannel<T, U> ch, int timeout = -1) {
  Either<T, U> v;
  if (!ch.TryReceive(out v, timeout)) throw new Exception(...);
  return v;
}
Tomas Petricek
This is a fantastic idea.
ChaosPandion
@ChaosPandion: Tomas is correct about the simplicity of only using one version of Send and Receive in the interface. This answer is also interesting to review. But it's a fantastically *terrible* idea to actually implement. You'd be using generics to indicate what you can send on your channels, followed by massive workarounds to prevent the compiler from enforcing it... what's the point?
280Z28
I was assuming that the original inteface declaration is what the author of the question actually needs. If it is better design the type differently (to support just receiving values of one type and sending values of the other type), then this is of course too complicated.
Tomas Petricek
@Tomas, @280Z28 - If I were keeping my original design this is what I would have used.
ChaosPandion
+3  A: 

The main problem is you are trying to view the duplex channel from both ends at the same time. Data travels both ways on a duplex channel, but there are still definite endpoints. What you send on one end is what you receive on the other.

public interface IDuplexChannel<TSend, TReceive>
{
    void Send(TSend data);
    TReceive Receive();
}

That said, you should be using WCF anyway, especially since you're using .NET 4.0.

Edit: Pictures

        "PERSON" A                                     "PERSON" B
            O            int ----------------->            O
           -|-           <-------------- string           -|-
           / \                                            / \
IDuplexChannel<int, string>                     IDuplexChannel<string, int>
280Z28
Why would I use WCF?
ChaosPandion
Especially since I am just experimenting with inter-thread communication?
ChaosPandion
However, I do see your point about the semantics of the interface name.
ChaosPandion
Even though it doesn't answer my original question it does resolve the question by pointing out the flaws in my design. Eric Lippert gets partial credit.
ChaosPandion
@ChaosPandion: The NetNamedPipeBinding can be used for this: http://msdn.microsoft.com/en-us/library/system.servicemodel.netnamedpipebinding.aspx
280Z28
That's way too *enterprisey* for messing around with after work.
ChaosPandion
I totally glossed over the fact that your actual example is sending something different than it receives.
ChaosPandion
@ChaosPandion: Exactly, because if you wanted to send and receive the same thing it would have been called `IDuplexChannel<TData>`.
280Z28
@ChaosPandion: Added a picture
280Z28
It's a shame I can't up-vote you again. That is a perfect "picture".
ChaosPandion
My actual solution involved 2 channels for each "actor" so I can simplify things a bit...
ChaosPandion