English 中文(简体)
不能改变类型:为什么需要两次投放?
原标题:Cannot convert type: why is it necesssary to cast twice?

鉴于这一高度简化的例子:

abstract class Animal { }

class Dog : Animal
{
  public void Bark() { }
}
class Cat : Animal
{
  public void Mew() { }
}

class SoundRecorder<T> where T : Animal
{
  private readonly T _animal;

  public SoundRecorder(T animal) { _animal = animal; }

  public void RecordSound(string fact)
  {
    if (this._animal is Dog)
    {
      ((Dog)this._animal).Bark(); // Compiler: Cannot convert type  T  to  Dog .
      ((Dog)(Animal)this._animal).Bark(); // Compiles OK
    }
  }
}

编辑们为什么抱怨单一类型的<代码>(Dog)this_animal? 我有理由想到,汇编者似乎需要帮助,做两 cast。 <代码>-animal 不能成为以外的任何东西?

Of course this question is motivated by a real life example where I had to modify existing code in a way that a similar cast was the most convenient way to do it, without refactoring the whole lot. (Yes, using composition rather than inheritance ;) ).

最佳回答

问题在于,汇编者可以保证,可以把 _子带给Dog,因为你给出的唯一限制是动物或动物继承动物。 因此,汇编者实际上想到:如果你构造了<条码>;Cat>,那么投放作业就是无效的。

令人不舒服的是(或不是)编辑们的智慧,足以确保你通过事先检查,安全地保护你的法典,使其无法到达。

如果你将特定动物作为实际动物加以储存,那就是一个问题,因为汇编者总是允许任何从基类到衍生型的动物。 汇编者有意允许从Dog向Cat公司投放一 cast

http://www.ohchr.org。 见Joon Skeets对更具体的解释的答复。

问题回答

EDIT: This is an attempted restatement of Polity s answer - I think I know what he s trying to say, but I could be wrong.

My original answer (below the line) is still in some ways the canonical one: the compiler rejects it because the language specification says it has to :) However, in an attempt to guess the view of the language designers (I ve never been part of the C# design committee, and I don t think I ve asked them about this, so it really is guesswork......) here goes......

We re used to thinking about the validity of conversions "at compile time" or "at execution time". Usually implicit conversions are ones which are compile-time-guaranteed to be valid:

string x = "foo";
object y = x;

That can t go wrong, so it s implicit. If something can go wrong, the language is designed so that you have to tell the compiler, "Trust me, I believe it ll work at execution time even though you can t guarantee it now." Obviously there s a check at execution time anyway, but you re basically telling the compiler you know what you re doing:

object x = "foo";
string y = (string) x;

Now the compiler already prevents you from attempting conversions which it believes can never work1 in a useful way:

string x = "foo";
Guid y = (Guid) x;

The compiler knows there s no conversion from string to Guid, so the compiler doesn t believe your protestations that you know what you re doing: you clearly don t2.

So those are the simple cases of "compile time" vs "execution time" checking. But what about generics? Consider this method:

public Stream ConvertToStream<T>(T value)
{
    return (Stream) value;
}

What does the compiler know? Here we have two things which can vary: the value (which varies at execution time, of course) and the type parameter T, which is specified at a potentially different compile time. (I m ignoring reflection here, where even T is only known at execution time.) We may compile the calling code later, like this:

ConvertToStream<string>(value);

At that point, the method doesn t make sense if you replace the type parameter T with string, you end up with code which wouldn t have compiled:

// After type substitution
public Stream ConvertToStream(string value)
{
    // Invalid
    return (Stream) value;
}

(Generics don t really work by doing this sort of type substitution and recompiling, which would affect overloading etc - but it can sometimes be a helpful way of thinking about it.)

The compiler can t report that at the time when the call is compiled - the call doesn t violate any constraints on T, and the body of the method should be viewed as an implementation detail. So if the compiler wants to prevent the method from ever being called in a way which introduces a non-sensical conversion, it has to do so when the method itself is compiled.

Now the compiler/language isn t always consistent in this approach. For example, consider this change to the generic method, and the "following type substitution when called with T=string" version:

// Valid
public Stream ConvertToStream<T>(T value)
{
    return value as Stream;
}

// Invalid
public Stream ConvertToStream(string value)
{
    return value as Stream;
}

This code does compile in the generic form, even though the version after type substitution doesn t. So maybe there s a deeper reason. Maybe in some cases there simply wouldn t be suitable IL to represent the conversion - and the easier cases aren t worth making the language more complicated for......

1 It sometimes gets this "wrong", in that there are times when a conversion is valid in the CLR but not in C#, such as int[] to uint[]. I ll ignore these edge cases for the moment.

2 Apologies to those who dislike the anthropomorphisation of the compiler in this answer. Obviously the compiler doesn t really have any emotional view of the developer, but I believe it helps get the point across.


简单的答复是,汇编者抱怨说,语言说明必须如此。 规则载于C# 4光谱第6.2.7节。

特定类型的参数(<代码>)有以下明确的换算: T:

......

  • From a type parameter U to T, provided T depends on U. (See section 10.1.5.)

这里的<代码>Dogt t依T,因此不允许转换。

我怀疑这一规则是为了避免一些模糊的玉米案件而制定的,在这种情况下,如果你能够合理地认为它应当是一种有效的转换尝试,那就会造成痛苦,但我怀疑,编纂这一逻辑会使语言更加复杂。

注:替代案文可以是使用as而不是is-then-cast:

Dog dog = this._animal as Dog;
if (dog != null)
{
    dog.Bark();
}

d 我辩称,单单进行转换,就更清洁。

这可能是因为你具体指出,通用型号Animal,因此,SoundRecorder可作为通用型号列入。 因此,汇编者不能允许你将<条码>Animal任意排入其他动物类别。 如果你想避免双重投票,则努力做到如下:

var dog = _animal as Dog;

if(dog != null)
{
    dog.Bark();
}

条涉及通用参数的表述专题

There is no explicit type conversion exist between Animal to Dog since your constrains says T must be of type Animal. Though Dog ‘Is a’ an Animal , the compiler doesn t know that T is Dog. Therefore, it doesn t let you cast.

You can either approach this through implicit conversion

implicit operator Animal(Dog myClass) 

or can use something like below

Dog d = _animal as Dog;




相关问题
Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

NSArray s, Primitive types and Boxing Oh My!

I m pretty new to the Objective-C world and I have a long history with .net/C# so naturally I m inclined to use my C# wits. Now here s the question: I feel really inclined to create some type of ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

How to Use Ghostscript DLL to convert PDF to PDF/A

How to user GhostScript DLL to convert PDF to PDF/A. I know I kind of have to call the exported function of gsdll32.dll whose name is gsapi_init_with_args, but how do i pass the right arguments? BTW, ...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...