我正在创建一个函数,我需要传递一个对象,以便它可以被函数修改。有什么区别:
public void myFunction(ref MyClass someClass)
and
public void myFunction(out MyClass someClass)
我应该用哪个,为什么?
我正在创建一个函数,我需要传递一个对象,以便它可以被函数修改。有什么区别:
public void myFunction(ref MyClass someClass)
and
public void myFunction(out MyClass someClass)
我应该用哪个,为什么?
当前回答
创作时间:
我们创建调用方法Main()
(2)它创建一个List对象(这是一个引用类型对象)并将其存储在变量myList中。
public sealed class Program
{
public static Main()
{
List<int> myList = new List<int>();
在运行时:
(3)运行时在堆栈#00处分配一个内存,足够宽来存储一个地址(#00 = myList,因为变量名实际上只是内存位置的别名)
(4)运行时在内存位置#FF的堆上创建一个列表对象(所有这些地址都是为了举例)
(5) Runtime会将对象的起始地址#FF存储在#00(或者在word中,将List对象的引用存储在指针myList中)
回到创作时间:
(6)然后我们将List对象作为参数myParamList传递给被调用的方法modifyMyList,并将一个新的List对象赋给它
List<int> myList = new List<int>();
List<int> newList = ModifyMyList(myList)
public List<int> ModifyMyList(List<int> myParamList){
myParamList = new List<int>();
return myParamList;
}
在运行时:
(7)运行时启动被调用方法的调用例程,作为它的一部分,检查参数的类型。
(8)在找到引用类型后,它在堆栈#04处分配一个内存,用于别名参数变量myParamList。
然后它将值#FF也存储在其中。
(10) Runtime在内存位置#004的堆上创建一个列表对象,并用这个值替换#04中的#FF(或者在这个方法中取消引用原始的list对象并指向新的list对象)
#00中的地址不会改变,并保留对#FF的引用(或者原始的myList指针不会被干扰)。
ref关键字是一个编译器指令,用于跳过(8)和(9)的运行时代码的生成,这意味着不会为方法参数分配堆。它将使用原始的#00指针对位于#FF的对象进行操作。如果原始指针没有初始化,运行时将停止抱怨它不能继续,因为变量没有初始化
out关键字是一个编译器指令,它与ref几乎相同,只是在(9)和(10)处略有修改。编译器期望参数是未初始化的,并将继续使用(8),(4)和(5)在堆上创建一个对象,并将其起始地址存储在参数变量中。不会抛出任何未初始化的错误,并且之前存储的任何引用都将丢失。
其他回答
扩展狗和猫的例子。带有ref的第二个方法更改调用者引用的对象。所以叫“猫”!!
public static void Foo()
{
MyClass myObject = new MyClass();
myObject.Name = "Dog";
Bar(myObject);
Console.WriteLine(myObject.Name); // Writes "Dog".
Bar(ref myObject);
Console.WriteLine(myObject.Name); // Writes "Cat".
}
public static void Bar(MyClass someObject)
{
MyClass myTempObject = new MyClass();
myTempObject.Name = "Cat";
someObject = myTempObject;
}
public static void Bar(ref MyClass someObject)
{
MyClass myTempObject = new MyClass();
myTempObject.Name = "Cat";
someObject = myTempObject;
}
注意,在函数内部传递的引用参数是直接处理的。
例如,
public class MyClass
{
public string Name { get; set; }
}
public void Foo()
{
MyClass myObject = new MyClass();
myObject.Name = "Dog";
Bar(myObject);
Console.WriteLine(myObject.Name); // Writes "Dog".
}
public void Bar(MyClass someObject)
{
MyClass myTempObject = new MyClass();
myTempObject.Name = "Cat";
someObject = myTempObject;
}
这会写狗,而不是猫。因此,您应该直接在someObject上工作。
From the standpoint of a method which receives a parameter, the difference between ref and out is that C# requires that methods must write to every out parameter before returning, and must not do anything with such a parameter, other than passing it as an out parameter or writing to it, until it has been either passed as an out parameter to another method or written directly. Note that some other languages do not impose such requirements; a virtual or interface method which is declared in C# with an out parameter may be overridden in another language which does not impose any special restrictions on such parameters.
从调用者的角度来看,c#在很多情况下假设调用带有out形参的方法时,会导致传入的变量在未被读取之前就被写入。当调用用其他语言编写的方法时,这个假设可能不正确。例如:
struct MyStruct
{
...
myStruct(IDictionary<int, MyStruct> d)
{
d.TryGetValue(23, out this);
}
}
如果myDictionary标识了一个用c#以外的语言编写的dictionary <TKey,TValue>实现,即使MyStruct s = new MyStruct(myDictionary);看起来像一个任务,它可能会留下未修改的s。
注意用VB编写的构造函数。NET与c#中的方法不同,它不假设被调用的方法是否会修改任何out参数,并且无条件地清除所有字段。上面提到的奇怪行为不会发生在完全用VB或完全用c#编写的代码中,但是当用c#编写的代码调用用VB.NET编写的方法时就会发生。
假设多姆因为TPS报告的备忘录出现在彼得的隔间里。
如果Dom是一个参考参数,他就会有一份备忘录的打印副本。
如果多姆是一个多余的论据,他会让彼得打印一份新的备忘录,让他随身携带。
由于您传递的是引用类型(类),因此不需要使用ref,因为默认情况下只传递对实际对象的引用,因此您总是要更改引用后面的对象。
例子:
public void Foo()
{
MyClass myObject = new MyClass();
myObject.Name = "Dog";
Bar(myObject);
Console.WriteLine(myObject.Name); // Writes "Cat".
}
public void Bar(MyClass someObject)
{
someObject.Name = "Cat";
}
只要传入一个类,如果你想在方法中改变对象,就不必使用ref。