【问题标题】:Interfaces, Inheritance, Implicit operators and type conversions, why is it this way?接口、继承、隐式运算符和类型转换,为什么会这样?
【发布时间】:2015-08-26 12:35:57
【问题描述】:

我正在使用一个名为 DDay ICal 的类库。 它是在 Outlook 日历和许多更多系统中实现的 iCalendar 系统的 C# 包装器。 我的问题源于我在这个系统上所做的一些工作。

这里有 3 个有问题的对象

  • IRecurrencePattern - 接口
  • RecurrencePattern - IRecurrencePattern 接口的实现
  • DbRecurPatt - 具有隐式类型运算符的自定义类

IRecurrencePattern:未显示所有代码

public interface IRecurrencePattern
{
    string Data { get; set; }
}

RecurrencePattern:未显示所有代码

public class RecurrencePattern : IRecurrencePattern
{
    public string Data { get; set; }
}

DbRecurPatt:未显示所有代码

public class DbRecurPatt
{
    public string Name { get; set; }
    public string Description { get; set; }

    public static implicit operator RecurrencePattern(DbRecurPatt obj)
    {
        return new RecurrencePattern() { Data = $"{Name} - {Description}" };
    }
}

令人困惑的部分:在整个 DDay.ICal 系统中,他们使用ILists 来包含日历中每个事件的重复模式集合,自定义类用于从数据库中获取信息,然后进行转换通过隐式类型转换运算符到递归模式。

但是在代码中,我注意到它在从List<DbRecurPatt> 转换为List<IRecurrencePattern> 时一直崩溃,我意识到我需要转换为RecurrencePattern,然后转换为IRecurrencePattern(因为还有其他类以不同方式实现 IRecurrencePattern 也包含在集合中

var unsorted = new List<DbRecurPatt>{ new DbRecurPatt(), new DbRecurPatt() };
var sorted = unsorted.Select(t => (IRecurrencePattern)t);

上面的代码不起作用,它在IRecurrencePattern上抛出了一个错误。

var sorted = unsorted.Select(t => (IRecurrencePattern)(RecurrencePattern)t);

这确实有效,所以我的问题是;为什么第一个不起作用? (有没有办法改进这种方法?)

我相信这可能是因为隐式运算符在RecurrencePattern 对象上而不是接口上,这是正确的吗? (我是接口和隐式运算符的新手)

【问题讨论】:

  • a List&lt;DbRecurPatt&gt; 是一个实际对象。您不能将其转换为 List&lt;IRecurrencePattern&gt;,因为有人可能会尝试将非 DbRecurPatt 填充到其中。
  • 应该注意的是,从RecurrencePatternIRecurrencePattern 的转换将引用同一个对象,但是从DbRecurPattRecurrencePattern 的转换创建了一个全新的对象。因此,在将其称为接口之前,您需要告诉它创建新对象。

标签: c# linq interface implicit-conversion data-conversion


【解决方案1】:

你基本上已经要求编译器这样做了:

  1. 我有这个:DbRecurPatt
  2. 我想要这个:IRecurrencePattern
  3. 请想办法从第 1 点到第 2 点。

编译器,即使它可能只有一个选择,也不允许你这样做。转换运算符明确表示DbRecurPatt 可以转换为RecurrencePattern,而不是IRecurrencePattern

编译器只检查所涉及的两种类型中的一种是否指定了如何从一种转换为另一种的规则,它不允许中间步骤。

由于没有定义允许将DbRecurPatt 直接转换为IRecurrencePattern 的运算符,编译器会将其编译为硬转换,通过接口将引用重新解释为引用,这将在运行时失败。

那么,下一个问题是:我该怎么做呢?答案是你不能。

编译器不允许您定义用户定义的与接口之间的转换运算符。 A different question here on Stack Overflow has more information.

如果你尝试定义这样的运算符:

public static implicit operator IRecurrencePattern(DbRecurPatt obj)
{
    return new RecurrencePattern() { Data = $"{obj.Name} - {obj.Description}" };
}

编译器会这样说:

CS0552
'DbRecurPatt.implicit operator IRecurrencePattern(DbRecurPatt)':不允许用户定义的与接口之间的转换

【讨论】:

  • 我认为您在隐式运算符代码示例中将I 留在了接口名称之外。
  • 该语句的最后一部分;您的意思是 return new IRecurrencePattern() 而不是 return new RecurrencePattern 因为在尝试该代码时,它可以工作?
  • @AlecScratch 不,我相信他的意思是签名是public static implicit operator IRecurrencePattern(DbRecurPatt obj)
  • 啊,好的,我现在明白了。我无论如何都没有意义,因为您可以实例化一个接口。德普。
  • 是的,我在签名中遗漏了一个 I,我从示例 LINQPad 程序中复制了错误的方法,但我想在方法中使用类是正确的。
【解决方案2】:

为什么第一个不起作用?

因为您要求运行时进行 两个 隐式转换 - 一个到 RecurrencePattern,一个到 IRecurrencePattern。运行时只会寻找 direct 隐式关系 - 它不会扫描所有可能的路线来让您要求它走。假设有多个隐式转换到实现IRecurrencePattern 的不同类型的类。运行时会选择哪一个?相反,它会强制您指定单个演员表。

这在 C# 语言规范的第 6.4.3 节中有记录:

评估用户定义的转换从不涉及多个 用户定义或提升的转换运算符。换句话说,一个 从 S 类型到 T 类型的转换永远不会首先执行 用户定义的从 S 到 X 的转换,然后执行用户定义的 从 X 到 T 的转换。

【讨论】:

    【解决方案3】:

    正如其他人已经指出的那样,您不能直接跳转DbRecurPattIRecurrencePattern。这就是为什么你最终会得到这个非常丑陋的双重演员:

    var sorted = unsorted.Select(t => (IRecurrencePattern)(RecurrencePattern)t);
    

    但是,为了完整起见,应该提到的是,可以从DbRecurPatt 转到IRecurrencePattern,而无需使用您当前的设计进行任何转换。只是这样做,你需要将你的表达式拆分成多个语句,这样做,代码确实变得相当丑陋。

    不过,很高兴知道您可以在没有任何强制转换的情况下做到这一点:

    var sorted = unsorted.Select( t => {
        RecurrencePattern recurrencePattern = t; // no cast
        IRecurrencePattern recurrencePatternInterface = recurrencePattern; // no cast here either
        return recurrencePatternInterface;
    });
    

    编辑

    感谢 Bill Nadeau 对这个想法的回答。您还可以从隐式转换及其编译时间保证中受益,同时通过这种方式编写代码来保持代码相当优雅:

    var sorted = unsorted
        .Select<DbRecurPatt, RecurrencePattern>(t => t) // implicit conversion - no cast
        .Select<RecurrencePattern, IRecurrencePattern>(t => t); // implicit conversion - no cast
    

    【讨论】:

    • 感谢您的回复,不过有一个问题,您知道这是否比 cast 方法更好吗?或者这纯粹是一个“好消息”?
    • 有一个优势(我个人认为这是一个很大的优势):如果您可以在没有任何强制转换的情况下编译代码,那么您可以放心,转换不会在运行时突然失败。将其与您的语句 (IRecurrencePattern)t 进行对比,该语句确实编译,但在运行时失败。但不可否认,代码更丑更冗长。
    • 那里仍然有两个转换,它们只是隐式转换,因为您将引用从一种类型的变量复制到另一种类型的变量。所以说“没有演员表”是不正确的。
    • @D Stanley:我明白你在说什么。但我认为castconversion 是有区别的。如果我说不需要转换,我会同意你的看法,那就错了。对于在标准文档中以这种方式使用术语的示例,您可以查看here“声明为显式的转换需要调用强制转换。”)、here“隐式转换 -- 无需转换"),或here
    • 万岁!人们喜欢我的想法!我认为对于 OP 的情况,两种技术的结合最终将是最好和最优雅的。老实说,只要避免硬演员,OP应该是好的。
    【解决方案4】:

    还有另一种途径可以实现您想要的。在方法调用上专门标记您的泛型参数,而不是让编译器推断您的泛型参数。您仍然可以避免强制转换,并且它可能比其他一些选项更简洁。唯一需要注意的是,您必须包含一个额外的 Linq 语句,如果这很重要,它将解决您的列表。

    var sorted = unsorted
       .Select<DbRecurPatt, RecurrencePattern>(t => t)
       .ToList<IRecurrencePattern>();
    

    您还可以将此答案与 sstan's 结合使用,以避免额外的 Linq 语句。

    【讨论】:

    • 我非常喜欢你的想法。我借用了您的想法并对我的答案进行了调整。基本上,我会链接 2 个Select 方法调用以避免强制转换,同时保持代码与 OP 的原始代码 sn-p 等效。结果很干净。
    【解决方案5】:

    ... 并回答关于隐式运算符的最后一个问题 - 不,您不能在接口上定义隐式运算符。此问题更详细地介绍了该主题:

    implicit operator using interfaces

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-10-14
      • 2012-09-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多