享受代码,享受人生

SOA is an integration solution. SOA is message oriented first.
The Key character of SOA is loosely coupled. SOA is enriched
by creating composite apps.
posts - 207, comments - 2288, trackbacks - 129, articles - 44
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

Generics Quiz

Posted on 2005-05-11 16:40 idior 阅读(2684) 评论(35)  编辑 收藏 所属分类: Windbey

下面这段代码为什么无法编译?

using System;
using System.Collections.Generic;
using System.Drawing;

namespace GenicTest
{
    
class Program
    
{

        
static void Main(string[] args)
        
{
            List
<Point> pl = new List<Point>();
            pl.Add(
new Point(1010));
            pl[
0].X = 42// <-- compiler error occurs here!
            Console.WriteLine(pl[0]);

        }

    }

}

如果你对C++很熟的话, 你再试一试用标准C++ 中的vector来代替List

这个例子其实反应了net2.0泛型的又一个让人失望的地方.

Feedback

#1楼    回复  引用    

2005-05-11 16:59 by 泡茶 [未注册用户]
首先声明我对.net2.0的范型没有进行深入的研究,但是对c++的stl还算熟悉。
在stl中,list确实不能使用[],正如贴主所说,vector才提供[]操作。

#2楼    回复  引用    

2005-05-11 17:10 by idior [未注册用户]
@泡茶
正巧我对STL不太熟. 我以为.net中的list就相当于c++的vector.
不过.net中的list并非不能使用[]. 而是有一些缺陷. ;)
具体答案等等再说喽.

这个quiz绝非想象中那么简单仅仅考你的c#语法而已, 需要深入研究的.
其中涉及了对值类型的box, unbox操作

#3楼    回复  引用  查看    

2005-05-11 17:29 by 柚子Nan      
这个语法看起来是那么的正确
也许.net 2.0 的Genic还没有对复杂的数据类型封装

只是做了int,string ,double 等。
而对于Point,Pair等没有做到深度的Box,unboxing

#4楼    回复  引用    

2005-05-11 17:41 by qq:383610 [未注册用户]
是不是还需要类型转换?
如果是的话,确实另人失望哦.

#5楼    回复  引用  查看    

2005-05-11 17:47 by 老翅寒暑      
看看 .net 中的定义吧
public sealed struct Point : System.ValueType

Point是值类型,你的 pl[0] 将返回一个临时的 Point 对象,然后你的语句 再把 42 赋给 这个临时对象的属性 X。然后离开这条语句之后,.net再把这个临时对象释放掉。你觉得这样的操作是你要的吗?

如果要实现你的意图,需要修改代码如下:
pl[0] = new Point(42, pl[0].Y);

#6楼    回复  引用  查看    

2005-05-11 18:47 by Leon      
老翅寒暑 说的太正确了,其实看编译器的错误报告
"Cannot modify the return value of 'System.Collections.Generic.List<System.Drawing.Point>.this[int]' because it is not a variable"
就应该可以想到这是编码的问题了。

#7楼    回复  引用    

2005-05-11 20:11 by idior [未注册用户]
老翅寒暑说的不错
如果是在.net1.0下, 在list中添加值对象将有一个box的过程, 取出对象的时候又有一个unbox的过程, 这时你得到的将是一个副本.

但是在2.0支持泛型的情况下还发生这种问题, 似乎不应该. 既然没有了box/ubbox操作,为什么不能获得原对象的地址而只能获得一个副本.

反之, 在c++中的泛型(vector)是不存在这样的问题的.
谁来试试c++下的List,以及.net2.0的C++/CLI , 在这给个答案吧.

#8楼    回复  引用    

2005-05-11 20:15 by idior [未注册用户]
@柚子Nan
窃以为你的说法不太正确.

之所以int string可以是大概是因为
p[0]=5;
就相当于p[0]=new int(5);

#9楼    回复  引用    

2005-05-11 20:56 by 泡茶 [未注册用户]
en,看了大家的发言,学到很多
看来是我肤浅了,呵呵

#10楼    回复  引用    

2005-05-12 08:40 by Ninputer [未注册用户]
这是因为默认属性(c#er喜欢叫它索引器,但索引器没有很好地给C#程序员带来“它是一种属性”的认识,所以我不这样叫)和其他属性一样,只能是按值传递的,它也是通过get和set访问的。你的语法将调用默认属性的get方法,这不可能返回给你存储在List<Point>中的那个实例的地址。不管是不是泛型,只要用了属性,就从根本上防止了对封装在类型内部的字段进行这种方式的更改。
你对.NET泛型遗憾,不如先思考一下使用C#属性的意义。

#11楼    回复  引用  查看    

2005-05-12 10:23 by 楚潇      
List<Point> pl = new List<Point>();
pl.Add(new Point(10, 10));
pl[0].X = 42; // <-- compiler error occurs here!
Console.WriteLine(pl[0]);

C#应该要做到让它编译能通过才行呀,我们程序员才不会管它是什么索引器或属性呢。很明显,这样写代码会带来很多方便,C#也就应该做到这样。

就像我们写的程序,客户才不管你用什么东东又怎么怎么地,他只要操作方便完成任务就行了。难道我们就可以因为用了某某技术就说:呀,这在我们系统里不行,因为我们用了XX技术?

#12楼    回复  引用    

2005-05-12 10:24 by dali [未注册用户]
是的这和泛型无关不用它也会有同样的错误

pl[0] = new Point(42, pl[0].Y); 是正确的做法

#13楼    回复  引用    

2005-05-12 10:34 by Ninputer [未注册用户]
“我们程序员才不会管它是什么索引器或属性呢”
这就是被惯坏了的表现

#14楼    回复  引用    

2005-05-12 10:41 by iFire [未注册用户]
赞同Ninputer的说法!

#15楼    回复  引用    

2005-05-12 11:54 by ... [未注册用户]
“我们程序员才不会管它是什么索引器或属性呢”

那你直接就写一句代码好了:
全部给我实现

#16楼    回复  引用    

2005-05-12 12:52 by holyfire [未注册用户]
我赞同楚潇的说法:

pl[0].X = 42;
从语义上pl[0].X无论从那一点都无法体现是个临时变量,用编译器的实现或者是设计上的缺陷来限制程序员是不对的。

我觉得pl[0].X可以实现成一个引用啊,这样我们也可以修改List里的值。

#17楼 [楼主]   回复  引用  查看    

2005-05-12 13:17 by idior      
using System;
using System.Collections.Generic;
using System.Drawing;


namespace GenicTest
{
    
class Program
    
{

        
static void Main(string[] args)
        
{
            Point[] pa 
= new Point[10];
            pa[
0= new Point(1010);
            pa[
0].X = 40;      // no problem here
            Console.WriteLine(pa[0]);
        }

    }

}


看看这个

#18楼    回复  引用    

2005-05-12 13:19 by holyfire [未注册用户]
你这个是arrary,而不是list啊,回答问题要看题目........

#19楼 [楼主]   回复  引用  查看    

2005-05-12 13:28 by idior      

using System;
using System.Collections.Generic;
using System.Drawing;


namespace GenicTest
{
    
class MyPoint
    
{
        
public MyPoint(int x, int y)
        
{
            
this.x = x;
            
this.y = y;
        }


        
private int x;

        
public int X
        
{
            
get return x; }
            
set { x = value; }
        }



        
private int y;

        
public int Y
        
{
            
get return y; }
            
set { y = value; }
        }

    
    }

    
class Program
    
{

        
static void Main(string[] args)
        
{

            List
<MyPoint> pl = new List<MyPoint>();
            pl.Add(
new MyPoint(1010));
            pl[
0].X = 40// <-- no problem
            Console.WriteLine(pl[0].X);   // output 40
        }

    }

}

#20楼 [楼主]   回复  引用  查看    

2005-05-12 13:49 by idior      
@Ninputer
请用属性的理论解释一下.

#21楼    回复  引用    

2005-05-12 14:05 by holyfire [未注册用户]
这个跟属性并没有什么关系

例子中的Point是个struct值类型的

而你的例子中的MyPoint是托管类,申明的其实是一个句柄和一个实例,然后实例放到托管堆里,句柄给你操作,放在List里的是句柄而不是实例

再来看List的索引实现
public virtual T this[
int index
] {get; set;}

返回的是什么,这要根据T的类型决定
我们来慢慢看看
Point -- struct 值类型

pa[0] = new Point(10, 10);
这一步产生了2个struct point
一个是new Point(10, 10);
一个是pa[0]所存储的对象
学过C++的很容易理解,值拷贝而已

那么读取他就有点花样了
首先返回的是struct
也就是产生一个临时的struct不是new Point(10, 10,也不是pa[0],但值的内容是(10,10),而编译器足够聪明,也提醒了你这点。(我觉得是微软也发现了这一不足,所以挖东墙补西墙而已,没有根本上解决问题),这也就是楼主提出的问题

而MyPoint -- class 托管类
pa[0] = new MyPoint(10, 10);
这一步产生了2个句柄和一个实例MyInst1,实例放在托管堆里,pa[0]和new Point(10, 10)都能操作这个事例
那读取的时候呢
返回一个临时句柄,内容和pa[0]一样,那也就可以操作MyInst1,所以设置属性值是可以的

#22楼    回复  引用    

2005-05-12 14:09 by Ninputer [未注册用户]
很简单,因为你的MyPoint是class,这就是引用类型了。你学过C#,对引用类型和值类型有什么区别应该很熟悉吧。如果MyPoint是引用类型,那么存在List(Of MyPoint)中的就不是MyPoint对象本身,而是它的引用。这时通过默认属性this取得的就是存储在List(Of MyPoint)中的,指向MyPoint托管堆中对象的引用,你当然可以对它的属性操作。但这并没有改变问题的实质,因为你无法从外部改变储存在List(Of MyPoint)的引用本身(即指针值),同样不违反封装性原则。分别解释两者的不同就是:

你第一个例子Point是值类型,那么默认属性this返回的就只是一个Point的值(注意这里已经不是引用,而是值),所以你当然无法对一个“值”的属性进行赋值。

现在你存的MyPoint是一个引用,那么默认属性this返回的是引用,你可以对该引用所值的真实对象进行操作(即你的代码),但你仍然不能改变引用本身。我举一个小小的例子
class SomeType1{public int A;}
class SomeType2
{
SomeType1 m_st1 = new SomeType1();
public SomeType1 this[int aValue]
{
get{return m_st1;}
}
}

现在,SomeType1是一个引用类型,我可以这样:
SomeType2 obj = new SomeType2();
obj[0].A = 1 //不会编译错误
但是,我们不能改变引用本身,比如有这样一个函数
void ChangeRef(ref SomeType1 a){a = new SomeType1();}
那么
ChangeRef(ref obj[0]); //错误

聪明的你应该懂了。

#23楼    回复  引用    

2005-05-12 14:17 by Ninputer [未注册用户]
而且,我可以说,只要属性还是只支持按值传递,C#的编写者根本无法实现让T为值类型时,obj[x].A = xxx这样的语法通过编译。至于数组,你不要以为数组的[]也是默认属性哦,那可是内建的语法,支持按引用传递的。

不管怎么说,这问题与泛型没什么关系。

#24楼 [楼主]   回复  引用  查看    

2005-05-12 14:19 by idior      
holyfire和Ninputer
从两个不同的角度说明了自己的看法. 我的思路和holyfire比较象.

看来要清楚解释这个问题, 还是需要IL来说明一切.

Allen Lee呢? ;)

#25楼    回复  引用  查看    

2005-05-12 19:17 by ccBoy      
别被形式所迷惑,看编译器的说明,Ninputer的理解是最贴近的
Ninputer 的说明足够详细了,尤其是最后一句。不过举的例子够差:)

而holyfire讲的就不怎么认同了。holyfire该不是CSDN的holyfire吧,要是这样就多有冒犯了:)

#26楼    回复  引用    

2005-05-12 20:40 by idior [未注册用户]
@ccBoy
你指的最后一句是这个吗?
-----
不管怎么说,这问题与泛型没什么关系。
-----

在没有泛型的时候问题很容易理解.
unbox, box之后肯定会产生一个拷贝, 所以对拷贝进行修改是不符合原有意愿的, 所以ms用编译出错提醒程序员.

在有了泛型之后, 没有unbox, box结果还是只能获得值,所以让人有些不爽, 特别是在c++的vector支持该功能, 不过看Ninputer 的解释确实可以理解. 所以该问题并不是完全与泛型无关.

我也和holyfire 一样习惯于从c++来考虑.

#27楼    回复  引用  查看    

2005-05-13 09:18 by 刘敏(Rustle Liu)      
holyfire 和 Ninputer讲的是一个意思啊,只不过理解的角度不同。

#28楼    回复  引用  查看    

2005-05-13 09:33 by Allen Lee      
我们应该选用恰当的方式来解释问题,而不是统统使用IL来剖析。我个人的想法与Ninputer的相近。

我认为你所提出的问题跟泛型本身没关系,也跟值类型的装拆箱没关系,关键在于方法返回值的语义上。无论是索引器(默认属性)还是属性,其实质都是方法,C#的方法返回值是按值语义的,于是

pl[0].X = 42;

产生一个Point的临时拷贝,对这个临时拷贝进行内容上的修改是毫无疑义的,正如老翅寒暑所说的,因为它将在不久的将来(生命期结束之时)自动销毁。

idior,我觉得你在这个问题上可能想得太多了 ^-^。

Hope this helps.

#29楼    回复  引用    

2005-05-13 13:01 by holyfire [未注册用户]
to:ccBoy

我是CSDN的holyfire^^

不要怕,尽管拍我的板砖

回到问题上来

我一开始就说明了,我不是想解释为什么C#的list会有这样的现象

而是pl[0].X = 42; 的语义问题,我个人是对C#感兴趣的,对C++也很喜欢,这里我不是对这2个语言进行比较(事实上C++中box引起的麻烦也不少),而是想说,这个的List在语义上是失败的,一个合格的语言因该不会产生语义混淆。我们总不能说,为了符合一个语言的习惯,我们要改变我们的思维方式吧。

#30楼    回复  引用  查看    

2005-05-13 17:47 by ccBoy      
最后一句是“只要属性还是只支持按值传递,C#的编写者根本无法实现让T为值类型时,obj[x].A = xxx这样的语法通过编译”,我想Ninputer说,"C#的编写者"是指"C#的编译器"

这就是我认同的原因,因为C++也许没有属性这样语义,所以从堆栈分配和对象激活的角度上分析,比较容易模糊,也许编译器只懂很少的规则。不过如大家所说List的这个语义表示是容易引起歧义的,特别是对于C++使用者( 其实几乎所有的人遇到这个表示都感到奇怪 idior的这个例子刚好又是值类型的)

to holyfire:
那我猜对了,欢迎欢迎:) 排砖就不敢了,大家一起探讨,学习

#31楼    回复  引用  查看    

2005-05-13 17:53 by ccBoy      
ps:

老翅寒暑是眼睛最尖的一个,佩服佩服(haha)

#32楼    回复  引用    

2006-02-20 17:46 by sp1234 [未注册用户]
奇怪,我只看到 object IList.this[int index] { get; set; } 这个定义。如果不是讨论这个函数产生的结果,而是讨论适配其它类型的,似乎根本就是不存在那个讨论对象。不知道这里再讨论什么哪?

#33楼    回复  引用    

2006-02-20 17:55 by sp1234 [未注册用户]
哦,看到了,public T this[int index] { get; set; }。看来要仔细看看。

#34楼    回复  引用    

2006-02-20 18:16 by sp1234 [未注册用户]
在我2005正式版上的错误信息是:

Expression is a value and therefore cannot be the target of an assignment.

#35楼    回复  引用    

2006-05-14 14:42 by witchery [未注册用户]
Expression is a value and therefore cannot be the target of an assignment.
....

标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2005-05-11 17:12 编辑过