【问题标题】:Generics and explicit/implicit operators泛型和显式/隐式运算符
【发布时间】:2012-04-25 13:32:57
【问题描述】:

考虑以下场景。

我有一个返回ISomething 的方法,但它可能是SomethingWrapped<Something>。 因此,我将结果转换为 Something 以使用它,但它失败了,任何关于为什么或如何解决它的帮助将不胜感激。

class Program
{
    static void Main(string[] args)
    {
        var a = new DerivedSomething();
        var b = (DerivedSomething)new Wrapped<DerivedSomething>(a); //success
        var c = (DerivedSomething)_GetSomething(false); //success, obsiously!
        var d = (DerivedSomething)_GetSomething(true); //Unable to cast object of type 'test_bed.Wrapped`1[test_bed.DerivedSomething]' to type 'test_bed.DerivedSomething'.
        var e = (DerivedSomething)(ISomething)new Wrapped<DerivedSomething>(a);  //Unable to cast object of type 'test_bed.Wrapped`1[test_bed.DerivedSomething]' to type 'test_bed.DerivedSomething'.

        var works = ((DerivedSomething)_GetSomething(false)).DoSomethingElse(); 
        var fails = ((DerivedSomething)_GetSomething(true)).DoSomethingElse(); //cast exception
    }

    private static ISomething _GetSomething(bool wrap)
    {
        var something = new DerivedSomething();
        return wrap ? new Wrapped<DerivedSomething>(something) : (ISomething)something;
    }
}

public interface ISomething
{
    void DoSomething();
}

public abstract class Something : ISomething
{
    public void DoSomething()
    {
        //some code
    }
}

public class DerivedSomething : Something
{
    public void DoSomething()
    {
        //some code
    }

    public void DoSomethingElse()
    {
        //some code
    }
}

public class Wrapped<T> : ISomething
    where T : ISomething
{
    private readonly T _something;

    public Wrapped(T something)
    {
        _something = something;
    }

    public void DoSomething()
    {
        _something.DoSomething();
    }

    public static explicit operator T(Wrapped<T> wrapped)
    {
        return wrapped._something;
    }
}

看来如果在尝试强制转换时类型暴露为接口,那么操作符就找不到了?

“简单”的解决方案是编写一个“解包”函数,该函数可以选择将Wrapped&lt;Something&gt; 解包到Something,但如果可能的话,我更喜欢使用运算符。

编辑

我认为问题的症结在于:在_GetSomething()之外,我不知道SomethingWrapped&lt;Something&gt;是否会被退回。

【问题讨论】:

  • 我不认为Wrapped&lt;Something&gt; 可以转换为Something。我编译了你的例子,并验证了这一点。第一次转换(变量b)应该失败......
  • 我猜你在问为什么 explicit operator T(Wrapped&lt;T&gt; wrapped) 不允许转换为传递的具体类型而不仅仅是约束类型。
  • @EthanBrown - 显式转换旨在执行转换,它在情况 B 中正常工作,问题是为什么它不适用于情况 D 或 E
  • I therefore cast the result to Something to use it:为什么你首先需要向下转换为Something?它和Wrapped&lt;T&gt; 都实现了ISomething 这一事实清楚地表明,至少在某种程度上它们应该是无法区分的。
  • @Anton,希望问题中的修改后的代码能更好地突出我的问题?

标签: c# generics casting operators


【解决方案1】:

显式转换是在编译时绑定的(一个额外的提示是转换运算符是静态的)。

试试

var f = (Something)(Wrapped<Something>)_GetSomething(true);

成功了

在您的情况下,编译器只知道您的类型是ISomething,并且不知道如何将ISomething 转换为Something,除非它已经是Something

通过将public class Wrapped&lt;T&gt; : ISomething 更改为public class Wrapped&lt;T&gt; : Something,您的示例可以正常执行,但是由于Wrapped&lt;T&gt; 已经是T,因此不会调用您的演员表。

注意:
explicit operator 不是强制转换,而是类型转换,它是一个调用的方法,并且该方法根据编译时类型(在您的情况下为 ISomething)进行解析。类型转换与类型转换具有相同的语法,这是一个令人困惑的根源。转换只是将现有对象分配给兼容类型的不同变量,而类型转换实际上返回一个新对象。

【讨论】:

  • Albin,您的示例可以工作,但将 false 传递给 _GetSomething() 的情况除外。在这种情况下,Something 无法转换为 Wrapped&lt;Something&gt;
  • 我的场景要求Wrapped&lt;T&gt;不继承,它纯粹消耗T。编译器是否仅通过其公开类型(ISomething - 作为_GetSomething() 的返回)而不是从方法发出的实际类型来理解类型?
  • @SimonLaing,你不能,因为必须在编译时知道类型(通过具有类型变量或强制转换)。我用一些关于强制转换和转换之间区别的注释更新了我的答案。
  • @SimonLaing,当您创建了接口ISomething 后,您最好一直使用该接口,而不是尝试转换为具体类型。
  • 我认为类型转换是我在这里需要的,因为我正在介绍一种方法来指示如何将Wrapped&lt;T&gt; 转换为T。有没有办法在不改变类型层次结构的情况下达到预期的结果?
【解决方案2】:

你的演员看起来像这样:

public static explicit operator T(Wrapped<T> wrapped)
{
        return wrapped._something;
}

你的投射像

var d = (Something)_GetSomething(true); //FAILS!

您不能转换为 Something,因为 SomethingT:ISomething具体实现。要完成这项工作,您需要编写:

var d = (ISomething)_GetSomething(true); //SUCCESS!

如果你真的想使用具体的类型,你可以定义泛型如:

public class Wrapped<T> : Something //Something and NOT ISomething 
    where T : ISomething
{
    .....
    .....
}

【讨论】:

  • Tigran,_GetSomething() 已经发出 ISomething,因此不需要演员表?
  • @SimonLaing:正确。我只是明确地表明您需要更改泛型的定义或使用此处提供的其他帖子中的双重转换。
  • 不幸的是Something 与我试图以通用方式包装的任意数量的类相关。因此,Wrapped&lt;T&gt; 类的数量需要与 Something 类的数量一样多,这是我急于避免的事情!
  • @SimonLaing:好的,但此时只是不要将转换为具体类型,而是使用 ISomething 之类的接口来引用具体实现。
  • @SimonLaing:我明白这一点。我的意思是,如果您以 OOP 方式构建您的代码,您应该不需要强制转换为具体类型,除非是极少数情况。因为所有具体类型共享 ISomething 中可用的同一组函数。所以如果你写 ISomething somethign = GetSomethign();谁在乎这背后的具体类型?随便用点什么。
【解决方案3】:
var d = (Something)(Wrapped<Something>)_GetSomething(true); 
var e = (Something)new Wrapped<Something>(a);

【讨论】:

  • Danny,您的示例可以工作,但将 false 传递给 _GetSomething() 的情况除外。在这种情况下,Something 无法转换为 Wrapped&lt;Something&gt;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-12-07
  • 1970-01-01
  • 1970-01-01
  • 2010-10-21
  • 1970-01-01
  • 2017-09-10
  • 2014-03-18
相关资源
最近更新 更多