在C#中,我一直认为非基元变量是通过引用传递的,而基元值是按值传递的。
因此,当将任何非基元对象传递给方法时,在方法中对该对象进行任何操作都会影响被传递的对象。(C# 101 的基础知识)
然而,我注意到当我传递一个System.Drawing.Image对象时,似乎并不是这种情况?如果我将一个system.drawing.image对象传递给另一个方法,并在该对象上加载图像,然后让该方法超出范围并返回到调用方法,则原始对象上没有加载该图像?
为什么会这样呢?
在C#中,我一直认为非基元变量是通过引用传递的,而基元值是按值传递的。
因此,当将任何非基元对象传递给方法时,在方法中对该对象进行任何操作都会影响被传递的对象。(C# 101 的基础知识)
然而,我注意到当我传递一个System.Drawing.Image对象时,似乎并不是这种情况?如果我将一个system.drawing.image对象传递给另一个方法,并在该对象上加载图像,然后让该方法超出范围并返回到调用方法,则原始对象上没有加载该图像?
为什么会这样呢?
对象根本没有被传递。默认情况下,参数被计算并且它的值作为传递的初始值传递(按值传递)给你调用方法的参数。现在重要的一点是,对于引用类型,该值是一个引用 - 一种访问对象(或null)的方式。对该对象所做的更改将从调用方可见。但是,在使用按值传递时(这是所有类型的默认值),将参数的值更改为引用不同的对象将不会可见。
如果您想使用按引用传递,无论参数类型是值类型还是引用类型,都必须使用out
或ref
。在这种情况下,实际上变量本身通过引用传递,因此参数使用与参数相同的存储位置 - 并且调用者可以看到对参数本身所做的更改。
所以:
public void Foo(Image image)
{
// This change won't be seen by the caller: it's changing the value
// of the parameter.
image = Image.FromStream(...);
}
public void Foo(ref Image image)
{
// This change *will* be seen by the caller: it's changing the value
// of the parameter, but we're using pass by reference
image = Image.FromStream(...);
}
public void Foo(Image image)
{
// This change *will* be seen by the caller: it's changing the data
// within the object that the parameter value refers to.
image.RotateFlip(...);
}
我有一篇文章详细解释了这个问题。基本上,“按引用传递”并不是你想象中的意思。
ref
和out
,那么说C#传递参数的方式与Java相同,即始终按值传递,这样说是否可以?与Java有什么区别吗? - broadbandmyobj
是某种引用类型...) - Jon Skeet已经添加了许多好的答案,我还想做出一些贡献,也许可以更加清晰地说明问题。
当您将实例作为参数传递给方法时,它会传递该实例的副本。如果您传递的实例是值类型
(驻留在堆栈
中),则传递该值的副本,因此,如果您修改它,则不会在调用者中反映出来。如果实例是引用类型,则将引用的副本(同样驻留在堆栈
中)传递给对象。因此,您有两个引用指向相同的对象。两者都可以修改该对象。但是,如果在方法体内实例化新对象,则您的引用副本将不再引用原始对象,而是引用您刚创建的新对象。因此,您最终会拥有2个引用和2个对象。
void Main()
{
int k = 0;
TestPlain(k);
Console.WriteLine("TestPlain:" + k);
TestRef(ref k);
Console.WriteLine("TestRef:" + k);
string t = "test";
TestObjPlain(t);
Console.WriteLine("TestObjPlain:" +t);
TestObjRef(ref t);
Console.WriteLine("TestObjRef:" + t);
}
public static void TestPlain(int i)
{
i = 5;
}
public static void TestRef(ref int i)
{
i = 5;
}
public static void TestObjPlain(string s)
{
s = "TestObjPlain";
}
public static void TestObjRef(ref string s)
{
s = "TestObjRef";
}
并且输出:
测试普通值:0
测试引用值:5
测试普通对象:测试
测试引用对象:测试引用对象
我猜这样做更清晰。 我建议下载LinqPad来测试类似的东西。
void Main()
{
var Person = new Person(){FirstName = "Egli", LastName = "Becerra"};
//Will update egli
WontUpdate(Person);
Console.WriteLine("WontUpdate");
Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");
UpdateImplicitly(Person);
Console.WriteLine("UpdateImplicitly");
Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");
UpdateExplicitly(ref Person);
Console.WriteLine("UpdateExplicitly");
Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");
}
//Class to test
public class Person{
public string FirstName {get; set;}
public string LastName {get; set;}
public string printName(){
return $"First name: {FirstName} Last name:{LastName}";
}
}
public static void WontUpdate(Person p)
{
//New instance does jack...
var newP = new Person(){FirstName = p.FirstName, LastName = p.LastName};
newP.FirstName = "Favio";
newP.LastName = "Becerra";
}
public static void UpdateImplicitly(Person p)
{
//Passing by reference implicitly
p.FirstName = "Favio";
p.LastName = "Becerra";
}
public static void UpdateExplicitly(ref Person p)
{
//Again passing by reference explicitly (reduntant)
p.FirstName = "Favio";
p.LastName = "Becerra";
}
而且应该输出
不会更新
名字:Egli,姓氏:Becerra
隐式更新
名字:Favio,姓氏:Becerra
显式更新
名字:Favio,姓氏:Becerra
System.Drawing.Image
类型的对象传递给方法时,实际上是传递了该对象的引用副本。因此,如果在该方法内部加载新图像,则使用新复制的引用进行加载。您并没有对原始对象进行更改。YourMethod(System.Drawing.Image image)
{
//now this image is a new reference
//if you load a new image
image = new Image()..
//you are not changing the original reference you are just changing the copy of original reference
}
你是如何将对象传递给方法的?
你在方法内部为对象创建了一个新实例吗?如果是这样,你需要在方法中使用ref
。
以下链接可以帮助你更好地理解:
http://dotnetstep.blogspot.com/2008/09/passing-reference-type-byval-or-byref.html
Employee e = new Employee();
e.Name = "Mayur";
//Passes the reference as value. Parameters passed by value(default).
e.ReferenceParameter(e);
Console.WriteLine(e.Name); // It will print "Shiv"
class Employee {
public string Name { get; set; }
public void ReferenceParameter(Employee emp) {
//Original reference value updated.
emp.Name = "Shiv";
// New reference created so emp object at calling method will not be updated for below changes.
emp = new Employee();
emp.Name = "Max";
}
}
public void main(String[] args)
)!namespace preparation
{
public class Program
{
public static void swap(ref int lhs,ref int rhs)
{
int temp = lhs;
lhs = rhs;
rhs = temp;
}
static void Main(string[] args)
{
int a = 10;
int b = 80;
Console.WriteLine("a is before sort " + a);
Console.WriteLine("b is before sort " + b);
swap(ref a, ref b);
Console.WriteLine("");
Console.WriteLine("a is after sort " + a);
Console.WriteLine("b is after sort " + b);
}
}
}
image.Load(filename)
,或者他们的意思是image = Image.Load(filename)
,其中image
是函数参数。 - StayOnTarget