【问题标题】:casting interface to T in c# [closed]在 C# 中将接口转换为 T [关闭]
【发布时间】:2012-11-22 07:48:38
【问题描述】:

我有一段代码感觉是这样的:(我已经去掉了这些方法,因为它们对这个问题没有帮助)

public abstract class A { }
public interface I { }
public class C : A , I { }
public class Program
{
    static void Update<T>(List<T> l,A a,I i,C c)
    {

        l.Add((T)a);//Error
        l.Add((T)i);
        l.Add((T)c);//Error
    }
}

抽象类和具体类的转换在编译时失败,但接口没有。

我知道我可以做类似l.Add((T)(object)a); 之类的事情来欺骗编译器,但我不明白为什么将接口转换为 T 有效。(如果它们都出错了,那么我可以假设它是某种类型检查错误)

我在java中试过了,它们都可以工作。

public static <T> void update(List<T> l,C c,I i,A a){
    l.add((T)c);//ok
    l.add((T)i);//ok
    l.add((T)a);//ok
}

仅仅是因为 c# 编译器的方式,还是我缺少一些关于 OOP 的概念?

【问题讨论】:

  • 如何调用更新?这里是什么?
  • 什么是 T,调用 Update() 方法的语句在哪里?
  • 是否有为 T 定义的约束?
  • 你没有把整个代码。 A 没有实现 I 所以你不能转换它。
  • 无论如何,有一个通用的然后进行手动转换的想法..?为什么,运行时异常比编译错误好得多?

标签: c# generics interface casting theory


【解决方案1】:

要让编译器理解你的代码,你必须用关键字where 约束T,像这样:

public abstract class A { }
public interface I { }
public class C : A, I { }
public class Program
{
    static void Update<T>(List<T> l, A a, I i, C c)
        // HERE IS THE CHANGE
        where T: C
    {

        l.Add((T)a);//Error
        l.Add((T)i);
        l.Add((T)c);//Error
    }
}

这是因为T 可以是默认的一切,甚至不是类,并且只有在约束之后代码的所有变体才会起作用。

只有如果你像我提到的那样限制你的方法,你可以编译这个,所以你唯一能做的就是:

var c = new C();
Update<C>(new List<C>(), c as A, (I)c, c);

这是无用,你应该重写你的代码。我认为,你应该使用更多的接口,更少的基类。

【讨论】:

  • A 是抽象类,它确实实现了I。你怎么能把它扔给我?
  • @DarthVader a 可以是 A 的子类(实际上需要是因为 A 是抽象的)并且可以另外实现 I
  • 如果它是一个没有实现I的类怎么办?
  • @DarthVader 它仍然不安全 - 您仍然可以在运行时获得 Cast Exception,但它更安全。
  • 理论上你也可以拥有D,它扩展了C,并在调用点使用D作为T
【解决方案2】:

在编译时,实现接口I 的类是否实际上可以是T 类型是未知的,因此编译器不会抱怨。但是 T 在您的代码中未绑定,编译器抱怨 T 需要声明边界以使代码更安全。

您可以解决编译时安全问题,并希望在调用站点上一切正常:

  l.Add((T)(object)a);// no compile time Error but possibly at runtime
  l.Add((T)i);  //no compile time Error but possibly at runtime
  l.Add((T)(object)c);// no compile time Error but possibly at runtime

但是牺牲编译时间检查是个坏主意。

改为声明T 的边界,例如:

static void Update<T>(List<T> l, A a, I i, C c)  where T: C {

  l.Add((T)a);// no compile time Error and less likely a runtime error
  l.Add((T)i);  //no compile time Error but still possibly at runtime
  l.Add((T)c);// no compile time Error and less likely a runtime error
}

很容易看出否则会出错

如果您调用 Update&lt;string&gt;(stringlist, a, i, c) - 否则它会中断,因为 A、I 和 C 都不能转换为字符串。

【讨论】:

  • A 是抽象类,它确实实现了I。你怎么能把它扔给我?
  • @DarthVader aA 的子类的一个实例(因为A 是抽象的)并且可以另外实现I——编译器无法知道这是否会成为这种情况,但也有可能。
【解决方案3】:

在 C# 中将任意类型的项转换为泛型类型参数不是一个好习惯。假设有一个不相关的类 D,而您调用 Update&lt;D&gt;(new List&lt;D&gt;(), new A(), new C()) - 如果您尝试将 A 的实例添加到 List&lt;D&gt; 中,您将在运行时遇到异常。

考虑一个非泛型的更简单的实现,您使用一些接口O,而不是泛型参数,例如:

public interface O { }
public interface I : O { }
public abstract class A : O { }
public class C : A, I { }

public class Program {
    static void Update(List<O> l, A a, I i, C c) {
        l.Add(a);
        l.Add(i);
        l.Add(c);
    }
}

【讨论】:

    【解决方案4】:

    参见6.2.7 C#语言规范(“Explicit conversions involved type parameters”),有解释:

    "

    对于给定的类型参数 T,存在以下显式转换:

    • 从任何接口类型到 T。在运行时,如果 T 是值类型,则转换将作为拆箱转换执行。否则,转换将作为显式引用转换或标识转换执行。

    上述规则不允许从不受约束的类型参数直接显式转换为非接口类型,这可能令人惊讶。此规则的原因是为了防止混淆并使此类转换的语义清晰。例如,考虑以下声明:

    class X<T>
    {
       public static long F(T t) {
          return (long)t;            // Error 
       }
    }
    

    如果允许将 t 直接显式转换为 int,人们可能很容易期望 X.F(7) 将返回 7L。但是,它不会,因为仅当类型在绑定时已知为数字时才会考虑标准数字转换。

    "

    【讨论】:

      猜你喜欢
      • 2013-08-25
      • 1970-01-01
      • 1970-01-01
      • 2019-05-23
      • 2014-03-15
      • 1970-01-01
      • 1970-01-01
      • 2011-08-05
      • 1970-01-01
      相关资源
      最近更新 更多