【问题标题】:What is the cleanest way to define this generic interface so it compiles?定义这个通用接口以便编译的最干净的方法是什么?
【发布时间】:2010-03-02 02:19:12
【问题描述】:

因此,具有相同参数但返回值不同的两个方法将无法编译。在不失去清晰度的情况下定义此接口的最佳方法是什么?

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);
}

我考虑过使用 out 参数,但使用起来会有点尴尬。

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);
}

通用版本,有点笨拙但可以。

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);
}

【问题讨论】:

  • 哪里不编译?错误是什么?
  • 如果有人曾经使用过 构造,你会玩得不亦乐乎。
  • @Eric - 回来指出我的愚蠢吧?这是我的代码实验项目中的一个很好的理由。
  • 我无意指出任何人的愚蠢。我只是想指出您在使用此界面设计时可能遇到的一个潜在问题。 CLR 对任何类型都有很多问题,这些问题可能会导致泛型构造下的签名统一;在 C# 泛型的原始设计中,即使声明您描述的类型也是非法的。
  • 对于这种签名统一导致的特别糟糕的情况,请参阅blogs.msdn.com/ericlippert/archive/2006/04/05/569085.aspx

标签: c# .net generics c#-4.0


【解决方案1】:

主要问题是您试图同时从两端查看双工通道。数据在双工通道上双向传输,但仍有明确的端点。您在一端发送的内容就是您在另一端收到的内容。

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

也就是说,您无论如何都应该使用 WCF,尤其是,因为您使用的是 .NET 4.0。

编辑:图片

        "PERSON" A                                     "PERSON" B
            O            int ----------------->            O
           -|-           <-------------- string           -|-
           / \                                            / \
IDuplexChannel<int, string>                     IDuplexChannel<string, int>

【讨论】:

  • 特别是因为我只是在尝试线程间通信?
  • 但是,我确实明白你关于接口名称语义的观点。
  • 尽管它没有回答我最初的问题,但它确实通过指出我设计中的缺陷来解决问题。 Eric Lippert 获得部分功劳。
  • @ChaosPandion:NetNamedPipeBinding 可用于此:msdn.microsoft.com/en-us/library/…
  • 这也太enterprisey下班后乱搞了。
【解决方案2】:

重命名这两个方法。它们仅在返回类型上有所不同。

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

注意,我没有测试过这个。试试这个。

R Receive<R>(int timeout = -1);

【讨论】:

  • 你的第二个建议是我的后备。我不喜欢它的原因是你必须专门定义类型Receive&lt;MyLongClassName&gt;
  • 如果你想要我的 2 美分...我说摆脱 Receive 方法而只使用 TryReceive。每当给出某种东西或TRYSOMETOMETHEM方法时,我总是更喜欢后者。 span>
  • @Josh - 我考虑过这样做,但是我决定添加非尝试方法,这样如果您无法处理失败,您就不必自己引发异常。
  • 我会说保留试用版和非试用版。如果您遵循标准的 .NET 模式,这两者具有不同的调用语义。我也会避免同时使用 Receive 和 TryReceive 参数...这违背了两者的目的和价值。
  • 为什么会说 TryReceive 会更常用呢?除非我需要明确处理从我调用它的同一位置接收失败的情况,否则我通常会使用 Receive 并让异常冒泡。您可以为这两种情况争论不休,这就是为什么这两种情况都存在于大多数 Microsoft 界面中的原因。归根结底,您使用哪一个取决于上下文,并且两者都具有价值。
【解决方案3】:

问题确实是有两个Receive 方法仅在返回类型上有所不同。因为您的类型表示双工通道,所以您必须复制接口中的所有内容 - 我相信更简单的方法是定义一个允许您表示 “T 或 U” 值的类型。这与Tuple&lt;T, U&gt; 非常相关,它现在在.NET 中允许您表示“T 和U”。该类型的签名可能如下所示:

// 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);
}

此类的示例用法可能如下所示:

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);

然后您可以使用更简单的界面来表示您的双工通道:

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); 
} 

正如 Josh 建议的那样,我也将摆脱 ReceiveSend 方法。为什么?因为它使接口的实现变得简单,您可以轻松地提供ReceiveSend 的实现,将TryReceiveTrySend 作为扩展方法。所以,你最终得到了一个界面:

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

扩展方法大致如下:

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;
}

【讨论】:

  • @ChaosPandion:Tomas 关于在界面中仅使用一个版本的发送和接收的简单性是正确的。这个答案也很有趣。但实际实施起来却是一个非常可怕的想法。您将使用泛型来指示您可以在频道上发送什么,然后使用大量变通方法来防止编译器强制执行它......有什么意义?
  • 我假设原始接口声明是问题作者实际需要的。如果更好地设计不同的类型(以支持只接收一种类型的值并发送另一种类型的值),那么这当然太复杂了。
  • @280Z28 - 如果我保留我的原始设计,我会使用它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-19
  • 1970-01-01
  • 2021-04-14
  • 2019-12-19
  • 2014-05-03
  • 1970-01-01
相关资源
最近更新 更多