提问人:Paul Wichtendahl 提问时间:12/23/2020 最后编辑:Paul Wichtendahl 更新时间:2/24/2022 访问量:221
对象传递引用与值 [已关闭]
Object Passing Reference vs Value [closed]
问:
为了给代码提供上下文,请想象一个多维数据集中的对象集合。这些对象是随机放置的,可以相互影响。计划了几个系列的测试事件,然后针对对象的多维数据集执行。只保留最佳结果。这不是真正的问题,而是集中问题的简化版本。示例代码
class Loc{
double UpDown
double LeftRight
double FrontBack
}
class Affects{
string affectKey
List<string> impacts //scripts that execute against properties
}
class Item{
Loc startLoc
Loc endLoc
List<string> affectedBy
string resultText // summary of analysis of changes
}
class ItemColl{
List<Item> myItems
}
class main{
ItemColl items
List<string> actions
void ProcessAffects(ItemColl tgt, List<string> acts){
// take actions against the tgt set and return
}
int IsBetter(ItemColl orig, List<Items> altered){
// compares the collection to determine "better one"
// positive better, negative worse, zero for no change
}
void DoThings(){
// original code
ItemColl temp = items
ProcessAffects(temp,actions)
IsBetter(temp,actions)
// the result was always zero - admittedly a duh error
}
}
当我添加一个复制对象的备用构造函数时,传入并对所有从属对象执行相同的操作,如
class ItemColl{
public ItemColl(){}
public ItemColl (ItemColl clone){
// do a deep copy
}
// partial code from main DoThings
// replaced ItemColl temp = items
// with
ItemColl temp = new ItemColl(items)
它解决了导致我第一个问题的问题。(感谢善意回答这个问题的人。我坚持的是是否有其他选择可以考虑?我希望这个重述有一个更好的重点,如果我没有利用一些新的效率,我想知道。
我完全删除了旧问题,并重新措辞了 face-palm 帖子。
答:
好吧,我不能发表评论,因为我的声誉太低了,但值类型通常是内置类型,例如 int、float ...
其他一切都是引用类型。无论 ref 关键字如何,引用类型始终是浅拷贝。
ref 关键字主要用于值类型或充当保护措施。
如果你想深度复制,Icloneable非常有用。
评论
everything else is reference type
您可以像编写类一样轻松地编写自己的课程。struct
ref keyword mainly served for value-type or act as a safeguard.
它对于引用类型也很有用。
所有自定义对象(派生自 tobject)都是“引用类型”。
以下关键字用于声明引用类型:
class
interface
delegate
C# 还提供以下内置引用类型:
dynamic
object
string
值类型可以是以下两种类型之一:
- 一种结构类型...
- 枚举类型 ...
因此,每当您创建类时,它始终是 Reference 类型。
EVERY 类型继承自 - 值类型和引用类型。Object
即使将其传递给具有引用参数的函数,与 RefChange 函数一样,这两个项目也会更改,并且两者在整数列表中具有完全相同的值。
关键字只是强制通过引用传递参数。与“引用类型”一起使用允许您重新分配传入引用的原始内容。请参阅 C# 中引用类型变量的“ref”有什么用?。ref
ref
不要将引用传递的概念与引用类型的概念混淆。这两个概念是不一样的。方法参数可以通过 ref 修改,无论它是值类型还是引用类型。当值类型通过引用传递时,没有装箱。
当然,当您传入值类型(例如 .ref
struct
如果要传递对象的副本,请创建一个重载构造函数,将原始对象传递给该构造函数,并在构造函数内部管理重要值的重复项。
这称为复制构造函数,并且是一种长期建立的模式,如果您想使用它。事实上,有一个关于它的 c# 9.0 的新功能:记录。
评论
ref
ref
ref
在进入参数之前,您需要一些背景知识:
背景
中有两种对象。NET-land、引用类型和值类型。两者之间的主要区别在于分配的工作方式。
值类型
将值类型实例分配给变量时,该值将复制到该变量中。基本数值类型(、、等)都是值类型。因此,在此代码中:int
float
double
decimal dec1 = 5.44m;
decimal dec2 = dec1;
dec1 = 3.1415m;
两个十进制变量 ( 和 ) 都足够宽,可以容纳一个十进制值数。在每种情况下,都会复制该值。最后,和 .dec
dec2
dec1 == 3.145m
dec2 == 5.44m
几乎所有值类型都声明为 a (是的,如果您可以访问 .NET 源,则为 )。与所有 .NET 类型一样,它们在装箱时会像派生自基类一样(它们的派生是通过 .两者都是引用类型(又名 )和引用类型,即使派生自的未装箱类型是值类型(这里发生了一点魔术)。struct
int
struct
object
System.ValueType
object
System.Object
System.ValueType
System.ValueType
所有值类型都是密封的/最终的 - 您不能对它们进行子类化。您也无法为它们创建默认构造函数 - 它们带有一个默认构造函数,用于将它们初始化为默认值。您可以创建其他构造函数(不会隐藏内置的默认构造函数)。
所有这些都是值类型。它们继承自 但属于值类型,其行为与其他值类型大致相同。enums
System.Enum
通常,值类型应设计为不可变的;并非所有人都是。
引用类型
引用类型的变量包含引用,而不是值。也就是说,有时认为它们持有一个值会有所帮助 - 只是该值是对托管堆上对象的引用。
当您赋值给引用类型的变量时,您正在赋值该引用。例如:
public class MyType {
public int TheValue { get; set; }
// more properties, fields, methods...
}
MyType mt1 = new MyType() {TheValue = 5};
MyType mt2 = mt1;
mt1.TheValue = 42;
在这里,和 变量都包含对同一对象的引用。当该对象在最后一行代码中发生变异时,您最终会得到两个变量,这两个变量都引用属性为 42 的对象。mt1
mt2
TheValue
声明为 a 的所有类型都是引用类型。通常,除了数值类型和 之外,您通常遇到的大多数(但不是全部)类型都是引用类型。class
enums
bools
任何声明为 a 或 an 的东西也是封面下的引用类型。有人提到.不存在纯粹类型为 .两者都可以声明为实现 - 它不会改变它们的值/引用类型性质,但存储为接口的结构将被装箱。delegate
event
interface
interface
structs
classes
interface
构造函数行为的差异
Reference 和 Value Types 之间的另一个区别是关键字在构造新对象时的含义。考虑这个类和这个结构:new
public class CPoint {
public float X { get; set; }
public float Y { get; set; }
public CPoint (float x, float y) {
X = x;
Y = y;
}
}
public struct SPoint {
public float X { get; set; }
public float Y { get; set; }
public CPoint (float x, float y) {
X = x;
Y = y;
}
}
它们基本上是相同的,只是 a(引用类型)和 a(值类型)。CPoint
class
SPoint
struct
当您创建使用 two float 构造函数的实例时(请记住,它会自动获得默认构造函数),如下所示:SPoint
var sp = new SPoint (42.0, 3.14);
发生的情况是构造函数运行并创建一个值。然后将该值复制到变量中(该变量的类型和大小足以容纳两个浮点数)。sp
SPoint
SPoint
如果我这样做:
var cp = new CPoint (42.0, 3.14);
发生了一些非常不同的事情。首先,在托管堆上分配内存,该堆足够大,可以容纳一个(即,足以容纳两个浮点数加上作为引用类型的对象的开销)。然后运行双浮点构造函数(并且该构造函数是唯一的构造函数 - 没有默认构造函数(程序员编写的附加构造函数隐藏了编译器生成的默认构造函数))。构造函数在托管堆上分配的内存中初始化该新内存。最后,创建对该新创建对象的引用并将其复制到变量 中。CPoint
CPoint
cp
参数传递
对不起,序言花了这么长时间。
除非另有说明,否则函数/方法的所有参数都按值传递。但是,不要忘记引用类型的变量的值是引用。
因此,如果我有一个声明为 ( 是上面声明的类):MyType
public void MyFunction(decimal decValue, MyType myObject) {
// some code goes here
}
以及一些看起来像以下内容的代码:
decimal dec1 = 5.44m;
MyType mt1 = new MyType() {TheValue = 5};
MyFunction (dec1, mt1);
发生的情况是,的值被复制到函数参数 () 中,并可在 中使用。如果有人在函数中更改了 的值,则不会在函数外部发生副作用。dec1
decValue
MyFunction
decValue
同样,但不同的是,将 的值复制到方法参数 中。但是,该值是对驻留在托管堆上的对象的引用。如果在方法中,某些代码变异了该对象(例如:),则 和 变量都引用的对象发生了变异,这会导致在函数外部可见的副作用。也就是说,一切都在按价值传递。mt1
myObject
MyType
myObject.TheValue=666;
mt1
myObject
按引用传递参数
您可以通过两种方式通过引用传递参数,即使用 or 关键字。在函数调用之前不需要初始化参数(而参数必须初始化)。在函数中,必须在函数返回之前初始化参数 - 参数可以初始化,但不需要初始化。这个想法是参数期望传入和传出函数(通过引用)。但参数被设计为一种从函数中传递某些内容的方式(通过引用)。out
ref
out
ref
out
ref
ref
out
如果我声明一个这样的函数:
public void MyByRefFunction(out decimal decValue, ref MyType myObject) {
decValue = 25.624; //decValue must be intialized - it's an out parameter
myObject = new MyType (){TheValue = myObject.TheValue + 2};
}
然后我这样称呼它
decimal dec1; //note that it's not initalized
MyType mt1 = new MyType() {TheValue = 5};
MyType mt2 = mt1;
MyByRefFunction (out dec1, ref mt1);
在该调用之后,将包含值 ;该值通过引用从函数中传出。dec1
25.624
按引用传递引用类型变量更有趣。函数调用后,mt1 将不再引用 equal to 5 创建的对象,它将引用 equal to 新创建的对象(函数内创建的对象)。现在,并将引用具有不同属性值的不同对象。TheValue
TheValue
5 + 2
mt1
mt2
TheValue
对于引用类型,当您正常传递变量时,您传递它的对象可能会发生突变(并且该突变在函数返回后可见)。如果逐个引用传递引用,则引用本身可能会发生变异,并且在函数返回后引用的值可能会有所不同。
评论
Nearly all value types are declared as a struct (yes, if you get access to the .NET sources, int is a struct)
哪种值类型不是结构?
public enum Stuff { one, two, red };
var (a, b, c) = (12, "yes", 3.14);
there's nothing really struct-ish about them
除了它们满足泛型的要求之外(请注意,根据泛型,结构可以是泛型 - 所以泛型很奇怪),具有结构复制行为等,等等;P但是,是的,你提出了一个有效的观点。我想我一直认为是 的同义词,而你让它稍微不那么黑白。;)struct
class
struct
value type
评论
int
Object
int
class
struct
IDisposiable
There is a lot to unpack here, and i am not sure the question as it stands is suitable for stackoverflow and has little use to future users.
我同意,主要是因为 meta.stackexchange.com/questions/39223/......问题中有很多不正确的陈述/断言,而且问题太多了 - 太宽泛了。