The Last Day Of Summer

.NET技术 C# ASP.net ActiveReport SICP 代码生成 报表应用 RDLC
posts - 305, comments - 1913, trackbacks - 76, articles - 3
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

今天被out参数玩了一把

Posted on 2005-12-22 18:59 Cure 阅读(2170) 评论(14)  编辑 收藏 所属分类: C#代码与实例

我们经常写这样的代码:

public class Class2
    {
        
private void InvokeTest()
        {
            Derive d 
= new Derive();
            Test(d);
        }
        
private void Test(Base b)
        {
        }
    }
    
class Base
    {
    }
    
class Derive : Base
    {
    }
    
class Derive1 : Base
    {
    }

使用基类来表示一个界面,然后传递子类。但是把这段代码改一改:

public class Class2
    {
        
private void InvokeTest()
        {
            Derive d 
= new Derive();
            Test( 
out d);
        }
        
private void Test( out Base b)
        {
            b = ......
        }
    }
    
class Base
    {
    }
    
class Derive : Base
    {
    }
    
class Derive1 : Base
    {
    }
按照平常的思路是没问题的,可是连编译都通不过。Why? 是继承体系不对?查一查,没问题。参数类型不对?也没问题,如果把Derive d = new Derive()换成Base d = new Base(),编译是没问题的。或者不使用out,编译也没问题。
费了老大劲,才找到关于这个问题的说明:
out要求传入的参数类型和声明的类型是一致的。原来的是out搞的鬼!对out参数子类到基类的转换是用不成的,
可是如果new的是基类,那么使用基类不就没意义了吗?
没办法,框架是人家写的,人家用了out参数我们也不能怎么样,不过这个问题得搞清楚。要传子类,还得自己想办法,绕个道:
    class Class1
    {
        [STAThread]
        
static void Main(string[] args)
        {
            BaseClass y ;
            Test( 
out y ,typeof(DeriveClass2) );
            y.WriteName();
            Console.ReadLine();
        }
        
static void Test(  out BaseClass y ,Type t )
        {
            y 
= BaseClass.CreateInstance(t);
        }
    }
    
class BaseClass
    {
        
public static BaseClass CreateInstance(Type deriveClassType)
        {
            
return (BaseClass)System.Activator.CreateInstance(deriveClassType);
        }
        
public void WriteName()
        {
            Console.WriteLine(
this.ToString());
        }
    }
    
class DeriveClass : BaseClass
    {
    }
    
class DeriveClass2 : BaseClass
    {
    }
要不是这次碰到这样的问题,我还真不知道out参数还有这问题呢?照着平常的思路老是走不通,还道是继承的有问题呢,还好,总算把原因搞清楚了

Feedback

#1楼    回复  引用    

2005-12-22 20:06 by 唐睿 [未注册用户]
那是因为 out 的参数需要传回来
传入是正确的,因为子类可以隐式转换成基类,但是传出来的时候就出问题了,因为基类必须强制类型转换成子类,所以是不过的

因此,按照这样的要求 out 参数必须使用确定的类型,只能这样

#2楼    回复  引用  查看    

2005-12-22 20:24 by windwolf      
楼主感到很奇怪吗?
这个不编译错误才怪
一个参数out了 那就是说它的传递就是双向了(ref也一样)。
传递的双向就意味这类型转化的双向,编译器要防你在外面的不安全类型转换,也要防你在里面的类型转化啊

#3楼 [楼主]   回复  引用  查看    

2005-12-22 22:35 by Cure      
不是感到奇怪,是因为平时从没这样用过,刚开始还找其他原因,最后在注意到这个问题,当然,自己想是能想通的,要不是亲身体验一次,恐怕真不会注意到。

#4楼 [楼主]   回复  引用  查看    

2005-12-22 23:32 by Cure      
楼上说的好,这个问题本不需要费多大劲,但是帮助里有明确说明这一点吗?那里有文档说不能这样作了?最后还是在C#规范里明确说明参数类型必须是一致的。我就是要确认一下,就是要一个答案而已,我不钻这个还作不了东西了?
楼上的水平我很佩服,没得说,但是你敢说没有你不知道的东西?

#5楼    回复  引用    

2005-12-22 23:59 by kor [未注册用户]
谈谈技术问题也能岔到教训人去的人,水平怕是也高不到哪里

#6楼    回复  引用  查看    

2005-12-23 07:58 by ※ABeen※      
交流问题吗!知道的就说,不知道就算了,说些不应该说的话多没有意思啊。

#7楼    回复  引用  查看    

2005-12-23 08:12 by 垃圾猪      
就是爱看不看,爱回不回,最看不起这种自以为是的人

#8楼 [楼主]   回复  引用  查看    

2005-12-23 09:12 by Cure      
问题关键是使用out参数后,不能传子类,Test函数所表达的界面就没有任何意义了,
也许这是一种不良设计,不过框架是别人写的,我们没修改权,能作的就是不要让这样的代码出现在自己的写的东西里

#9楼    回复  引用  查看    

2005-12-23 09:45 by quitgame      
是啊
闻道有先后 术业有专攻
难道每个问题都是打一出来就明白了吗?
没有必要伤害别人啊!!

#10楼    回复  引用  查看    

2005-12-23 11:11 by gray      
你可以将out参数作为函数的另一个返回值来理解

CreateDerive(out Base o)
<=> Base CreateDerive()

这样
Base b;
CreateDerive(out b);
Derive d = b as Derive;
<=>
Base b = CreateDerive();
Derive d = b as Base;




#11楼 [楼主]   回复  引用  查看    

2005-12-23 12:33 by Cure      
只要可以,我是不愿意,也不习惯用out参数的,如果非要返回多个值,我宁可多写几个函数,然后用他们的返回值.

换个角度说,对于out参数,我们需要换一种方式来达到目的

#12楼    回复  引用  查看    

2005-12-23 19:41 by Allen Lee      
这种参数在 Win32 API 中是很常见的,例如

LONG RegQueryValueEx(
HKEY hKey,
LPCTSTR lpValueName,
LPDWORD lpReserved,
LPDWORD lpType,
LPBYTE lpData,
LPDWORD lpcbData
);

其中 hKey、lpValueName 是传入的,lpType、lpData 是传出的(即 out),而 lpcbData 则是双向的(即 ref)。这种风格的函数使用 side-effect 来输出经处理的结果,而返回值则是用来告诉 Client 对结果的处理是否顺利。我们通常这样使用:

if (RegQueryValueEx( ... ) == ERROR_SUCCESS) { ... }

相对的,你发现 lpValueName、lpType、lpData 和 lpcbData 是相关的,可以把它们包装到一个类中,然后透过函数返回值来输出结果:

struct RegistryValue
{
LPCTSTR lpValueName;
LPDWORD lpType;
LPBYTE lpData;
LPDWORD lpcbData;
};

RegistryValue MyRegQueryValueEx(HKEY hKey);

你或许发现 MyRegQueryValueEx 可以作为 RegistryValue 的成员函数,因为它们俩是相关的,不过这是后话了。

ref/out 通常与值类型的参数配合使用,目的是为了把对该参数的内容所做的改变传递给 Client,即使用“传引用”方式。如果把 ref/out 应用到引用类型会怎样呢?拿上面的代码:

private void Test(out Base b)
{
b = ...
}

来举例,由于 Base 是引用类型,b 本身就是一个指针,它的值是它所指向的对象的地址,应用 out 使得你可以在方法内部改变 b 的值,于是在 Test 内部对 b 进行赋值实质上就是使 b 指向另一个对象。那么,你可以在 Test 中赋予 b 一个什么样的值呢?很明显,该值不能是 Base 的派生类的实例,而应该是 Base 的实例。这将使得 Client 的变量 d (类型为 Derive)指向一个 Base 的实例,即把基类的实例赋予派生类的变量,这是不允许的。对应的 C++ 代码是:

private: void Test(Base** b)
{
*b = ...
}

不同的是,在 C++ 中,你需要对 b 做一次解引用才能改变 Client 的变量的指向,做两次解引用才能访问被指向对象;而在 C# 中,对 b 直接赋值就可以改变 Client 的变量的指向,也可以通过 b 直接修改被指向对象的内容。

相对的,与以下 C# 代码:

private void Test(Base b) { ... }

对应的 C++ 代码是:

private: void Test(Base* b) { ... }

即使你在 Test 里面对 b 进行赋值,也不会影响 Client 的变量的指向。从这个角度来看,指针对被指向的对象来说是“传引用”的,而对指针本身来说是“传值”的。

虽然 C# 支持在引用类型上使用 ref/out,但在你使用该特性之前请自问一下必要性。

#13楼 [楼主]   回复  引用  查看    

2005-12-30 14:51 by Cure      
楼上的讲解太好了,其实我自己以为明白了,但是事实不是这样,继续学习

#14楼    回复  引用    

2006-06-14 16:26 by duhan [未注册用户]
感觉很困惑,不知道传一个out参数前为什么还要给他赋值。调用前赋的值进入test之后又不能用,test内部又是给他重新赋值,这么写有什么特殊的目的么?难道就是为了根据传入不同的类型而生成相应类型的对象?那简单的搞一个工厂不可以么?

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2006-07-12 18:36 编辑过


相关链接: