请问结构是按值传递,那么类是?根据情况来按值或引用传递么?

clover8 2010-06-17 03:27:57
[例子转自http://www.cftea.com/c/2009/06/EYR713KMPJ2U1QZL.asp]
List 类是 ArrayList 类的泛型等效类,某些情况下,用它比用数组和 ArrayList 都方便。

我们假设有一组数据,其中每一项数据都是一个结构。

public struct Item
{
public int Id;
public string DisplayText;
}
注意结构是不能给实例字段赋值的,即 public int Id = 1 是错误的。

using System.Collections.Generic;

List<Item> items = new List<Item>();

//添加
Item item1 = new Item();
item1.Id = 0;
item1.DisplayText = "水星";
items.Add(item1);

//添加
Item item2 = new Item();
item2.Id = 1;
item2.DisplayText = "地球";
items.Add(item2);

//修改
//这里使用的是结构,故不能直接用 items[1].DisplayText = "金星";,如果 Item 是类,则可以直接用。为什么呢?因为结构是按值传递的。
Item item = items[1];
item.DisplayText = "金星";
items[1] = item;
-------------------------------------
请看上面“这里使用的是结构,故不能直接用 items[1].DisplayText = "金星";,如果 Item 是类,则可以直接用。为什么呢?因为结构是按值传递的。”这段话,结构是按值传递,那么类是?根据情况来按值或引用传递么?

请问有没有谁能解释得更清楚一点。。。结构怎么就不能按引用传递?类是怎么判断按值还是引用传递的?
...全文
409 37 打赏 收藏 转发到动态 举报
写回复
用AI写文章
37 条回复
切换为时间正序
请发表友善的回复…
发表回复
clover8 2010-06-21
  • 打赏
  • 举报
回复
因为栈上可用资源比较小,一般就是1到2M,所以结构还不能用得太多吧?

顶一下,回头再来结贴~
兔子-顾问 2010-06-18
  • 打赏
  • 举报
回复
忘记贴了,AA定义

public struct AA
{
public int value;
public AA(int v)
{
value = v;
}
}
兔子-顾问 2010-06-18
  • 打赏
  • 举报
回复
处于好奇好玩,写个例子给大家看看。以证明我上面的分析。

private static void TestChangeStructList()
{
//定义一个泛型List
List<AA> datas = new List<AA>();
//添加2个元素,value分别为1,2
datas.Add(new AA(1));
datas.Add(new AA(2));
//打印出当前元素
Console.WriteLine("原始List:");
datas.ForEach(o => Console.WriteLine(o.value.ToString()));
//使用List的索引器赋值
datas.ForEach(o => o.value++);
//打印出当前元素
Console.WriteLine("使用List的索引器赋值");
datas.ForEach(o => Console.WriteLine(o.value.ToString()));
//反射List内部的值类型数组赋值
AA[] items = (AA[])((FieldInfo)(datas.GetType().GetMember("_items", BindingFlags.NonPublic | BindingFlags.Instance)[0])).GetValue(datas);
items[0].value++;
items[1].value++;
//打印出当前元素
Console.WriteLine("反射List内部的值类型数组赋值");
datas.ForEach(o => Console.WriteLine(o.value.ToString()));
}

结果

原始List:
1
2
使用List的索引器赋值
1
2
反射List内部的值类型数组赋值
2
3


还没看懂的,最后再讲一次。
List<T>[i]
这是叫做索引器的,索引器是一种属性,属性就是在调用方法,而值类型无法返回一个引用,返回的是值,所以索引器返回的,是你添加进去变量的副本。而因为值类型无法传递引用,所以添加实际也是使用副本的方式添加的。所以对于值类型的List<T>,索引器的结果,可以访问,可以修改,但无法直接存回去,如何保存?可以重新的赋值,例如
List<Point> points = new List<Point>();
points.Add(new Point());//0,0
修改呢,就整个重新复制
points[0] = new Point(1,1);
你不能修改一项points[0].X = 1;
这样不可以的。

希望这样说,各位不明白的能明白。明白的更明白。
jianuMan 2010-06-18
  • 打赏
  • 举报
回复
结构是值类型
类是引用类型

但是结构也可以用引用方式传递
用关键字 ref
  • 打赏
  • 举报
回复
学习了。。。
healer_kx 2010-06-17
  • 打赏
  • 举报
回复
在这个问题上C++给出的语法细致得多了,但是很不好掌握。
soaringbird 2010-06-17
  • 打赏
  • 举报
回复
ref 关键字使参数按引用传递。其效果是,当控制权传递回调用方法时,在方法中对参数的任何更改都将反映在该变量中。若要使用 ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字。
按引用传递值类型(如本主题前面所示)是有用的,但是 ref 对于传递引用类型也是很有用的。这允许被调用的方法修改该引用所引用的对象,因为引用本身是按引用来传递的。下面的示例显示出当引用类型作为 ref 参数传递时,可以更改对象本身。

skyaspnet 2010-06-17
  • 打赏
  • 举报
回复
学习。。。
doubleu2005 2010-06-17
  • 打赏
  • 举报
回复
已经很多了,就不补充了
兔子-顾问 2010-06-17
  • 打赏
  • 举报
回复
[Quote=引用 26 楼 sp1234 的回复:]
在调用一个方法时,不论你写ref或者不写,传递的都是一个引用。而对struct,都是复制一个副本。

不过我倒是不关心struct,我倒是关心对象作为参数。“根据情况来按值或者引用传递”的误解就是对ref的误解。不论写不写它,对于对象做参数都是传递引用。或者你可以说,对象的引用被作为对象而传递了。
[/Quote]

非常贴切,但未必能理解。我举个例子描述一下sp1234大神的意思。
public class A
{
public int i = 0;
}

A obj = new A();
public void Foo(obj);

public void Foo(A a)
{
a.i = 5;//你可以修改a的i属性,因为a是传递的引用。但写不写ref可不同哦。
a = new A();//你可以给a重新赋值,但是a是传进来参数的副本,a的修改不过是形参,obj.i已然是5,不是0
}

A obj = new A();
public void Foo(ref obj);

public void Foo(ref A b)
{
b.i = 6;//b作为引用,修改i的值为6
b = new A();//你可以修改b的值,离开方法后,参数就变了,obj.i就是0了。
}
  • 打赏
  • 举报
回复
struct的问题就是一不小心,我们就只是修改副本,还不知道。就好象 #14 楼一样。这样所花费精力写的代码不少,还容易产生很多bug难以发现,因此最好不要使用struct。
  • 打赏
  • 举报
回复
在调用一个方法时,不论你写ref或者不写,传递的都是一个引用。而对struct,都是复制一个副本。

不过我倒是不关心struct,我倒是关心对象作为参数。“根据情况来按值或者引用传递”的误解就是对ref的误解。不论写不写它,对于对象做参数都是传递引用。或者你可以说,对象的引用被作为对象而传递了。
兔子-顾问 2010-06-17
  • 打赏
  • 举报
回复
今天看楼主帖子,督促自己看了msdn,恩。吧这个List<T>无法修改item的问题终于搞清楚了。happy
兔子-顾问 2010-06-17
  • 打赏
  • 举报
回复
可能还没明白么?
再说明白一点this._items[index],这个是的的确确你添加进去的那个结构体,但结构体是传值的,在函数调用 void Foo(StructValue o)
这里,你的o是传递的值,那么换一种写法
StructValue Foo()
{
StructValue f;
return f;//你认为这里返回的是f还是f的副本呢?想明白这个,结合List索引器的get代码,应该明白原因了,不要纠结了,换用类吧。
}
  • 打赏
  • 举报
回复
[Quote=引用楼主 clover8 的回复:]
请问有没有谁能解释得更清楚一点。。。结构怎么就不能按引用传递?类是怎么判断按值还是引用传递的?[/Quote]

首先要明白什么叫做按值、按引用传递?

这里去重复c语言规则就会出笑话了。当你在调用方法的参数上,假设传递的是对象,不写ref就是按值传递对象吗?写ref就是按引用传递对象吗?如果你这样理解就完全把c的概念张冠李戴到c#上了。这样,也就无法进一步理解值类型的数据的传递。
兔子-顾问 2010-06-17
  • 打赏
  • 举报
回复
为什么说吻合呢,我们看看List<T>的get方法

[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
public T get_Item(int index)
{
if (index >= this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
return this._items[index];
}


是什么意思?
return this._items[index];
this._items[index]已经是一个副本了。所以你通过List[i]访问的,就是副本,无法修改。
兔子-顾问 2010-06-17
  • 打赏
  • 举报
回复
sp1234所说的貌似以前看到过类似的建议

http://msdn.microsoft.com/zh-cn/library/ah19swz4(VS.80).aspx
结构还可以包含构造函数、常量、字段、方法、属性、索引器、运算符、事件和嵌套类型,但如果同时需要上述几种成员,则应当考虑改为使用类作为类型。


http://msdn.microsoft.com/zh-cn/library/saxz13w4(v=VS.80).aspx
结构与类共享几乎所有相同的语法,但结构比类受到的限制更多:

•在结构声明中,除非字段被声明为 const 或 static,否则无法初始化。

•结构不能声明默认构造函数(没有参数的构造函数)或析构函数。

结构的副本由编译器自动创建和销毁,因此不需要使用默认构造函数和析构函数。实际上,编译器通过为所有字段赋予默认值(参见默认值表)来实现默认构造函数。结构不能从类或其他结构继承。

结构是值类型 -- 如果从结构创建一个对象并将该对象赋给某个变量,变量则包含结构的全部值。复制包含结构的变量时,将复制所有数据,对新副本所做的任何修改都不会改变旧副本的数据。由于结构不使用引用,因此结构没有标识 -- 具有相同数据的两个值类型实例是无法区分的。C# 中的所有值类型本质上都继承自 ValueType,后者继承自 Object。

结构设计



请注意这个例子,MSDN的这个例子和楼主要的完全吻合。
如何:了解向方法传递结构和向方法传递类引用之间的区别(C# 编程指南)
  • 打赏
  • 举报
回复
或许个别从c++(而且也不是做应用系统,而是花一年时间才死抠一小段底层代码,整天谈论“效率”的人)刚刚转入c#是可能习惯使用struct。我对c#程序员的建议是:永远不要去主动去想要使用struct。
VRC289 2010-06-17
  • 打赏
  • 举报
回复
结构怎么就不能按引用传递?

这个是谁说的? 纯粹的胡说八道 使用ref参数不就是按照引用来传递吗
  • 打赏
  • 举报
回复
[Quote=引用 14 楼 dazhabai 的回复:]
这段代码没问题啊,楼主[/Quote]

虽然代码没有问题,那是因为你没有测试到“点子”上。如果你打印items[1]的内容,仍然是“地球”而不是“金星”!
加载更多回复(17)

110,577

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧