关于"ref"和"out"参数的定义已经有很多问题了,但它们看起来设计不好。有没有你认为裁判是正确的解决方案?
似乎你总是可以做些更干净的事情。有人能给我一个例子,说明这是解决问题的"最佳"解决方案吗?
在我看来,ref在很大程度上弥补了宣布新的效用类型的困难,以及将信息"附加"到现有信息的困难,这些都是c从其诞生以来通过linq、generics和匿名类型朝着解决问题迈出的巨大一步。
所以不,我不认为有太多明确的使用案例。我认为这在很大程度上是语言最初设计方式的遗物。
我认为在需要从函数和返回值返回某种类型的错误代码的情况下(如上所述),这仍然是有意义的,但没有其他任何东西(因此更大的类型是不合理的)。如果我在项目中到处这样做,我可能会为Thing和Error定义一些通用的包装器类型。-代码,但在任何给定的情况下,ref和out都可以。
- 如果"返回代码+值"构造在项目中被广泛使用,那么我同意使用包装器类型的想法。
嗯,ref通常用于专门的案例,但我不会称之为冗余或C的遗留功能。例如,您会看到它(和out在xna中使用了很多。在XNA中,Matrix是struct和相当大的一个(我相信是64字节),通常最好是将它传递给使用ref的函数,以避免复制64字节,但只有4或8个字节。专业的C功能?当然。不再有太多的用途,还是设计不好?我不同意。
一个领域是使用小型实用程序功能,例如:
1
| void Swap<T>(ref T a, ref T b) { T tmp = a; a = b; b = tmp; } |
我看不到任何"更清洁"的替代品。当然,这并不完全是体系结构级别。
P/Invoke是我唯一能真正想到的地方,在这里你必须使用ref或out。其他情况下,他们可以方便,但如你所说,一般有另一种,更清洁的方式。
如果您想要返回多个对象,那么由于一些未知的原因,这些对象并没有绑定到一个对象中。
1
| void GetXYZ( ref object x, ref object y, ref object z); |
编辑:Divo建议使用out参数更适合这个。我得承认,他说得有道理。我将把这个答案留在这里,作为一个记录,这是一个不准确的解决方案。在这种情况下,胜于裁判。
- 我认为在大多数情况下,您可以重新设计您的代码,将相似的东西组合成更好的类型,或者以更清晰、希望仍然有效的方式独立地返回每件事情。
- @我完全同意。我认为,如果你这样做,你要么有一个方法,可能做的太多,或一个坏的对象模型。说了这句话,我相信有人这样做是有原因的。
- 在这种情况下,我更喜欢使用out参数,因为当时的意图更为明确。
- @迪沃是的,也许你是对的。
- 区别在于"传递给ref参数的参数必须首先初始化。这与out不同,后者的参数在传递之前不必显式初始化(请参见msdn.microsoft.com/en-us/library/t3c3bfhx.aspx)。
ref的一种设计模式是双向访问。
假设您有一个Storage类,可以用来加载或保存各种基元类型的值。它要么处于Load模式,要么处于Save模式。它有一组称为Transfer的重载方法,下面是一个处理int值的示例。
1 2 3 4 5 6 7
| public void Transfer(ref int value)
{
if (Loading)
value = ReadInt();
else
WriteInt(value);
} |
对于其他原始类型也有类似的方法-bool、string等。
然后,在一个需要"可转移"的类上,您将编写如下的方法:
1 2 3 4 5 6
| public void TransferViaStorage(Storage s)
{
s.Transfer(ref _firstName);
s.Transfer(ref _lastName);
s.Transfer(ref _salary);
} |
同一个方法既可以从Storage加载字段,也可以将字段保存到Storage中,具体取决于Storage对象所处的模式。
实际上,您只是列出了所有需要传输的字段,因此它非常接近声明性编程,而不是命令式编程。这意味着您不需要编写两个函数(一个用于读取,一个用于写入),并且考虑到我在这里使用的设计是顺序相关的,那么确定字段总是以相同的顺序读取/写入非常方便。
一般来说,当一个参数被标记为ref时,您不知道该方法是读还是写,这允许您设计在两个方向中的一个方向上工作的访问者类,以对称方式调用(即,使用访问的方法不需要知道访问者的方向模式)类正在操作)。
比较:属性+反射
为什么要这样做而不是将字段属性化并使用反射来自动实现等效的TransferViaStorage?因为有时反射速度很慢,足以成为瓶颈(但总是进行概要分析以确保这一点——这几乎从来都不是真的,而且属性更接近于声明性编程的理想)。
它的真正用途是在创建结构时。C中的结构是值类型,因此在值传递时总是完全复制。如果您需要通过引用传递它,例如出于性能原因或因为函数需要对变量进行更改,您将使用ref关键字。
我可以看到是否有人有一个具有100个值的结构(显然已经有问题了),您可能希望通过引用传递它来防止复制100个值。如果返回大型结构并重写旧值,则可能会出现性能问题。
- 在引用类型的情况下,它不是很冗余,因为当您使用"ref"传递时,获取变量的函数可以更改您的引用,使其指向完全不同的引用(或为空)。如果您不使用"ref",它只能改变您的对象的现有实例。
- @mquander是否确实需要ref关键字来执行此操作?我想如果没有裁判你也能做到
- 这个信息是错误的!在C中,默认情况下,引用按值传递。参见yoda.arachsys.com/csharp/parameters.html
- 正如divo链接中详细描述的那样,"ref"关键字确实是必要的。如果不使用"ref"关键字,则实际拥有的是对同一对象的两个独立引用。因此,对对象的更改将从函数外部可见,但不会更改引用(即使其无效)。
- @警卫:为了避免混淆,我从你的答案中去掉了不正确的部分。请随意还原和编辑:-)
- 有关具有100个值的结构的信息不正确。结构(前面还有ref)仍然复制到堆栈的顶部,以便被调用的方法完成其工作。ref关键字只表示在被调用的方法完成其工作后,应再次复制该结构。在您的例子中,我将使用引用类型对象(其中只传递引用)
- @taoufik:你有没有一个引用复制了用ref传递的结构?根据C规范,以下保留:"为引用参数传递的参数必须是变量,并且在方法执行期间,引用参数表示与参数变量相同的存储位置。"(参见download.microsoft.com/download/3/8/8/&hellip;的1.6.6.1参数)
我认为最好的用途是那些你通常看到的;你需要有一个值和一个"成功指示器",这不是一个函数的例外。
- 但这难道不是out参数的完美候选吗?在我看来,out比ref清楚得多,而且在bcl中也是首选(就像在.TryParse方法中一样)。
- 是真的。很明显我现在需要睡觉了,这里很晚了…;)
当您必须使用"ref"关键字时,有一个明显的例子。如果对象是定义的,但不是在要调用的方法的范围之外创建的,并且要调用的方法应该执行'new'来创建它,则必须使用'ref'。例如,{object a; Funct(a);} {Funct(object o) {o = new object; o.name ="dummy";}不会对object 'a'做任何事情,也不会在编译或运行时抱怨它。它什么都做不了。{object a; Funct(ref a);} {Funct(object ref o) {o = new object(); o.name ="dummy";}将使'a'成为新的对象,名称为"dummy。但如果'new'已经完成,则不需要参考(但如果提供,则有效)。{object a = new object(); Funct(a);} {Funct(object o) {o.name ="dummy";}
使用"ref"关键字的明显原因是当您希望通过引用传递变量时。例如,将值类型(如System.Int32)传递给方法并更改其实际值。更具体的用法可能是当您想交换两个变量时。
1 2 3 4
| public void Swap(ref int a, ref int b)
{
...
} |
使用"out"关键字的主要原因是从一个方法返回多个值。我个人更喜欢将值包装在一个专门的结构或类中,因为使用out参数会产生相当难看的代码。用"out"传递的参数就像用"ref"传递的参数。
1 2 3 4
| public void DoMagic(out int a, out int b, out int c, out int d)
{
...
} |