Posted on 2005-05-11 16:40
idior 阅读(2684)
评论(35) 编辑 收藏 所属分类:
Windbey
Feedback
首先声明我对.net2.0的范型没有进行深入的研究,但是对c++的stl还算熟悉。
在stl中,list确实不能使用[],正如贴主所说,vector才提供[]操作。
@泡茶
正巧我对STL不太熟. 我以为.net中的list就相当于c++的vector.
不过.net中的list并非不能使用[]. 而是有一些缺陷. ;)
具体答案等等再说喽.
这个quiz绝非想象中那么简单仅仅考你的c#语法而已, 需要深入研究的.
其中涉及了对值类型的box, unbox操作
这个语法看起来是那么的正确
也许.net 2.0 的Genic还没有对复杂的数据类型封装
只是做了int,string ,double 等。
而对于Point,Pair等没有做到深度的Box,unboxing
是不是还需要类型转换?
如果是的话,确实另人失望哦.
看看 .net 中的定义吧
public sealed struct Point : System.ValueType
Point是值类型,你的 pl[0] 将返回一个临时的 Point 对象,然后你的语句 再把 42 赋给 这个临时对象的属性 X。然后离开这条语句之后,.net再把这个临时对象释放掉。你觉得这样的操作是你要的吗?
如果要实现你的意图,需要修改代码如下:
pl[0] = new Point(42, pl[0].Y);
老翅寒暑 说的太正确了,其实看编译器的错误报告
"Cannot modify the return value of 'System.Collections.Generic.List<System.Drawing.Point>.this[int]' because it is not a variable"
就应该可以想到这是编码的问题了。
老翅寒暑说的不错
如果是在.net1.0下, 在list中添加值对象将有一个box的过程, 取出对象的时候又有一个unbox的过程, 这时你得到的将是一个副本.
但是在2.0支持泛型的情况下还发生这种问题, 似乎不应该. 既然没有了box/ubbox操作,为什么不能获得原对象的地址而只能获得一个副本.
反之, 在c++中的泛型(vector)是不存在这样的问题的.
谁来试试c++下的List,以及.net2.0的C++/CLI , 在这给个答案吧.
@柚子Nan
窃以为你的说法不太正确.
之所以int string可以是大概是因为
p[0]=5;
就相当于p[0]=new int(5);
en,看了大家的发言,学到很多
看来是我肤浅了,呵呵
这是因为默认属性(c#er喜欢叫它索引器,但索引器没有很好地给C#程序员带来“它是一种属性”的认识,所以我不这样叫)和其他属性一样,只能是按值传递的,它也是通过get和set访问的。你的语法将调用默认属性的get方法,这不可能返回给你存储在List<Point>中的那个实例的地址。不管是不是泛型,只要用了属性,就从根本上防止了对封装在类型内部的字段进行这种方式的更改。
你对.NET泛型遗憾,不如先思考一下使用C#属性的意义。
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技术?
是的这和泛型无关不用它也会有同样的错误
pl[0] = new Point(42, pl[0].Y); 是正确的做法
“我们程序员才不会管它是什么索引器或属性呢”
这就是被惯坏了的表现
“我们程序员才不会管它是什么索引器或属性呢”
那你直接就写一句代码好了:
全部给我实现
我赞同楚潇的说法:
pl[0].X = 42;
从语义上pl[0].X无论从那一点都无法体现是个临时变量,用编译器的实现或者是设计上的缺陷来限制程序员是不对的。
我觉得pl[0].X可以实现成一个引用啊,这样我们也可以修改List里的值。
你这个是arrary,而不是list啊,回答问题要看题目........
这个跟属性并没有什么关系
例子中的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,所以设置属性值是可以的
很简单,因为你的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]); //错误
聪明的你应该懂了。
而且,我可以说,只要属性还是只支持按值传递,C#的编写者根本无法实现让T为值类型时,obj[x].A = xxx这样的语法通过编译。至于数组,你不要以为数组的[]也是默认属性哦,那可是内建的语法,支持按引用传递的。
不管怎么说,这问题与泛型没什么关系。
holyfire和Ninputer
从两个不同的角度说明了自己的看法. 我的思路和holyfire比较象.
看来要清楚解释这个问题, 还是需要IL来说明一切.
Allen Lee呢? ;)
别被形式所迷惑,看编译器的说明,Ninputer的理解是最贴近的
Ninputer 的说明足够详细了,尤其是最后一句。不过举的例子够差:)
而holyfire讲的就不怎么认同了。holyfire该不是CSDN的holyfire吧,要是这样就多有冒犯了:)
@ccBoy
你指的最后一句是这个吗?
-----
不管怎么说,这问题与泛型没什么关系。
-----
在没有泛型的时候问题很容易理解.
unbox, box之后肯定会产生一个拷贝, 所以对拷贝进行修改是不符合原有意愿的, 所以ms用编译出错提醒程序员.
在有了泛型之后, 没有unbox, box结果还是只能获得值,所以让人有些不爽, 特别是在c++的vector支持该功能, 不过看Ninputer 的解释确实可以理解. 所以该问题并不是完全与泛型无关.
我也和holyfire 一样习惯于从c++来考虑.
holyfire 和 Ninputer讲的是一个意思啊,只不过理解的角度不同。
我们应该选用恰当的方式来解释问题,而不是统统使用IL来剖析。我个人的想法与Ninputer的相近。
我认为你所提出的问题跟泛型本身没关系,也跟值类型的装拆箱没关系,关键在于方法返回值的语义上。无论是索引器(默认属性)还是属性,其实质都是方法,C#的方法返回值是按值语义的,于是
pl[0].X = 42;
产生一个Point的临时拷贝,对这个临时拷贝进行内容上的修改是毫无疑义的,正如老翅寒暑所说的,因为它将在不久的将来(生命期结束之时)自动销毁。
idior,我觉得你在这个问题上可能想得太多了 ^-^。
Hope this helps.
to:ccBoy
我是CSDN的holyfire^^
不要怕,尽管拍我的板砖
回到问题上来
我一开始就说明了,我不是想解释为什么C#的list会有这样的现象
而是pl[0].X = 42; 的语义问题,我个人是对C#感兴趣的,对C++也很喜欢,这里我不是对这2个语言进行比较(事实上C++中box引起的麻烦也不少),而是想说,这个的List在语义上是失败的,一个合格的语言因该不会产生语义混淆。我们总不能说,为了符合一个语言的习惯,我们要改变我们的思维方式吧。
最后一句是“只要属性还是只支持按值传递,C#的编写者根本无法实现让T为值类型时,obj[x].A = xxx这样的语法通过编译”,我想Ninputer说,"C#的编写者"是指"C#的编译器"
这就是我认同的原因,因为C++也许没有属性这样语义,所以从堆栈分配和对象激活的角度上分析,比较容易模糊,也许编译器只懂很少的规则。不过如大家所说List的这个语义表示是容易引起歧义的,特别是对于C++使用者( 其实几乎所有的人遇到这个表示都感到奇怪 idior的这个例子刚好又是值类型的)
to holyfire:
那我猜对了,欢迎欢迎:) 排砖就不敢了,大家一起探讨,学习
ps:
老翅寒暑是眼睛最尖的一个,佩服佩服(haha)
奇怪,我只看到 object IList.this[int index] { get; set; } 这个定义。如果不是讨论这个函数产生的结果,而是讨论适配其它类型的,似乎根本就是不存在那个讨论对象。不知道这里再讨论什么哪?
哦,看到了,public T this[int index] { get; set; }。看来要仔细看看。
在我2005正式版上的错误信息是:
Expression is a value and therefore cannot be the target of an assignment.
Expression is a value and therefore cannot be the target of an assignment.
....