Effective C#: Item 3: Prefer the is or as Operators to Casts

 

Item 3: Prefer the is or as Operators to Casts

C#是强类型语言.我们要尽量避免类型转换.

有时我们必须要在runtime检查一个变量的类型.比如有时你要用到一些.Net framework提供的方法,这些方法需要用到System.Object类型的参数.你需要把这些object (方法的参数)向下cast成其他的类型(类或者interface),这时你有两个基本方式可以选择,一是使用as操作符,二是使用C语言风格的cast.两者也可以结合成一个更加保险的方法,就是先用is 操作符来测试类型的转换,然后再用cast或者as 操作符进行转换.

正确的选择应该是使用as操作符来进行类型转换. as操作符比碰运气型的cast更加的安全,而且在runtime更加的高效. asis操作符并不能进行所有的用户定义的类型转换, 只有当runtime类型和目标类型一致时转换操作才会成功.它们永远不会为了满足程序调用请求而创建一个新的object.

在下例中,你需要把一个object转换成一个MyType的实例,你可以这样实现:

                  object o = Factory.GetObject( );<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 

                  // Version one:

                  MyType t = o as MyType;

 

                  if ( t != null )

                  {

                        // work with t, it's a MyType.

                  }

                  else

                  {

                        // report the failure.

                  }

也可以这样写:

                  object o = Factory.GetObject( );

 

                  // Version two:

                  try

                  {

                        MyType t;

                        t = ( MyType ) o;

                        if ( t != null )

                        {

                              // work with T, it's a MyType.

                        }

                        else

                        {

                              // Report a null reference failure.

                        }

                  }

                  catch

                  {

                        // report the conversion failure.

                  }

第一个方法明显更加的简单易读,而且也没有try/catchoverhead.代码更加的高效.我们注意到cast版本不仅要检查转换后的object是否为null,还要catch异常.as操作符版本却不用. 这是因为使用cast, null可以被转换成任何一种reference类型,但是当应用as操作符在一个null reference上时会返回null. 所以as操作符只需检查一下返回的reference是否为null, 而不用catch异常.

as操作符和cast操作符最大的不同是如何对待用户定义的转换. asis操作符只会检查被转换的objectruntime类型,而不做任何其他的工作. 如果这个object不是目标类型,或者不是目标类型的子类型, 操作失败并终止. cast操作符却不同,它会把object转换成目标类型, 这包括所有的numeric转换, 比如从long转换成short, object的一些信息就在这种转换中丢失了.

而且当cast用户自定义类型时也会有同样的问题.比如:

      public class SecondType

      {

            private MyType _value;

 

            // other details elided

 

            // Conversion operator.

            // This converts a SecondType to

            // a MyType, see item 29.

            public static implicit operator

                  MyType( SecondType t )

            {

                  return t._value;

            }

      }

假设我们用Factory.GetObject()生成了一个SecondTypeobject,并把它转换成MyType类型.

                  object o = Factory.GetObject( );

 

                  // o is a SecondType:

                  MyType t = o as MyType; // Fails. o is not MyType

 

                  if ( t != null )

                  {

                        // work with t, it's a MyType.

                  }

                  else

                  {

                        // report the failure.

                  }

 

                  // Version two:

                  try

                  {

                        MyType t1;

                        t = ( MyType ) o; // Fails. o is not MyType

                        if ( t1 != null )

                        {

                              // work with t1, it's a MyType.

                        }

                        else

                        {

                              // Report a null reference failure.

                        }

                  }

                  catch

                  {

                        // report the conversion failure.

                  }

两个版本都会失败.但是cast却执行了用户定义的转换.这种假象使你认为cast成功了.但实际上它是失败的,因为编译器会根据编译时object的类型来生成代码.编译器对运行时object的类型一无所知.它只是把o当作System.Object的一个实例.编译器没有发现从System.ObjectMyType可行的转换.它检查System.ObjectMyType的定义,因为缺少用户定义的转换信息,编译器生成代码来检查o的运行时的类型,然后检查它是不是MyType类型.因为oSecondType类型,所以转换失败.编译器并不检查o在运行时的类型是否可以转换成MyType类型.

如果你想让转换成功,可以这样写代码:

                  object o = Factory.GetObject( );

 

                  // Version three:

                  SecondType st = o as SecondType;

                  try

                  {

                        MyType t;

                        t = ( MyType ) st;

                        if ( t != null )

                        {

                              // work with T, it's a MyType.

                        }

                        else

                        {

                              // Report a null reference failure.

                        }

                  }

                  catch

                  {

                        // report the failure.

                  }

你永远也不应该写这样丑陋的代码,但这也是一个常见的问题.尽管你永远不应该写这样的代码,但你可以用System.Object来当作一个进行转换操作的function的参数,比如:

            object o = Factory.GetObject( );

 

            DoStuffWithObject( o );

 

            private void DoStuffWithObject( object o2 )

            {

                  try

                  {

                        MyType t;

                        t = ( MyType ) o2; // Fails. o is not MyType

                        if ( t != null )

                        {

                              // work with T, it's a MyType.

                        }

                        else

                        {

                              // Report a null reference failure.

                        }

                  }

                  catch

                  {

                        // report the conversion failure.

                  }

            }

用户自定义的转换只作用于编译时object的类型,而不时运行时的类型. 至于运行时是否存在o2MyType类型的转换, 编译器不知道也根本不关心. 但当st类型不同时,这个语句有着不同的表现:

t = ( MyType ) st;

上面的语句会调用用户自定义的转换,从而造成转换成功的假象. 但使用as操作符的语句却有着一致的表现.所以应该尽量的使用as操作符.如下面的语句:

t = st as MyType;
 
 

 

事实上,如果stMyType之间不存在继承关系的话,而是通过一个用户自定义的转换来进行类型转换,那么编译器会报告一个错误.

现在你知道了应该尽可能的使用as操作符.但也有一些情况不能使用它.as操作符不能作用于value type.下面的这个语句不会通过编译:

object o = Factory.GetValue( );

                  int i = o as int; // Does not compile.

因为int是值类型,永远不能为null.那么如果o不是整数类型的话, i里面应该存什么值呢? 所以你不能使用as操作符.你可以用下面这种变通的方式:

                  object o = Factory.GetValue( );

                  int i = 0;

                  try

                  {

                        i = ( int ) o;

                  }

                  catch

                  {

                        i = 0;

                  }

但你不必一定这样一来做,不要忘了is操作符,你可以在转换之前先判断o的类型:

                  object o = Factory.GetValue( );

                  int i = 0;

                  if ( o is int )

                        i = ( int ) o;

如果o不是整数类型,那么is操作符返回false. is操作符作用于null arguments上时,永远返回false;

但你应该只在你不能使用as操作符转换类型时使用is,否则就是重复的, 比如:

                  // correct, but redundant:

                  object o = Factory.GetObject( );

 

                  MyType t = null;

                  if ( o is MyType )

                        t = o as MyType;

上面的代码和下面的代码是等效的:

                  // correct, but redundant:

                  object o = Factory.GetObject( );

 

                  MyType t = null;

                  if ( ( o as MyType ) != null )

                        t = o as MyType;

可以看出,进行了两次转换,低效而且重复.如果你已经决定了要使用as操作符来转换类型,那么只需检查返回值是否为null就可以了.

现在你已经明白了as, iscast,那么foreach循环用的是什么操作符呢?

            public void UseCollection( IEnumerable theCollection )

            {

                  foreach ( MyType t in theCollection )

                        t.DoStuff( );

            }

foreach用的实际上是cast操作符.上面的代码可以重写成下面的代码:

            public void UseCollection( IEnumerable theCollection )

            {

                  IEnumerator it = theCollection.GetEnumerator( );

                  while ( it.MoveNext( ) )

                  {

                        MyType t = ( MyType ) it.Current;

                        t.DoStuff( );

                  }

            }

这是因为foreach要用cast来支持value typereference type. 如果使用as操作符的话,foreach语句仍表现相同的行为,但会抛出BadCastException,因为as不能作用于值类型上.

因为IEnumerator.Current返回一个System.Object类型的object,而这个object不具备转换操作,所以并不能用于这个测试. SecondType类型的collection也不能用于UseCollection()因为转换会失败. Foreach语句并不检查collectionobject的运行时类型是否支持这种转换,它只检查IEnumerator.Current所返回的System.Object类型是否支持到目标类型(本例中的MyType)之间的转换.

最后,有时你想知道一个object确切的类型,而不只是关心这个object是否可以转换成目标类型. 因为as操作符对于所有从目标类型继承而来的类型的转换都返回true. GetType()方法返回object运行时的类型,它比as或者is操作符提供的测试都要更严格. 它返回的是object的确切类型.

再看一下UseCollection():

            public void UseCollection( IEnumerable theCollection )

            {

                  foreach ( MyType t in theCollection )

                        t.DoStuff( );

            }

如果你创建一个叫NewType的类,这个类继承MyType,那么NewType objectscollection也可以在UseCollection()中很好的工作.

      public class NewType : MyType

      {

            // contents elided.

      }

如果你的意图是写出一个可以使用所有MyType类型(自身或继承而来)function,这没有什么.但如果你的意图是写一个只接受MyType类型自身的function的话,你就要用确切的类型来进行比较. 在本例中,你可以在foreach循环里做. 知道运行时确切的类型只有在做equality测试时是非常重要的.在大多数其他的情况下,asis操作符提供的isinst比较是语法上正确的.

好的OO经验告诉我们要尽量避免类型的转换,但有时类型转换是必需的.在这种情况下,尽量的使用asis操作符来表达你的意图. 不同的类型强制转换有不同的规则,asis却在绝大多数情况下都是正确的,而且它们只有在object是正确的类型时才转换成功. Cast操作符会带来一些副作用,而且转换的成功与失败往往出乎意料.

  

 

 

本系列文章只是作者读书笔记,版权完全属于原作者 (Bill Wagner),任何人及组织不得以任何理由以商业用途使用本文,任何对本文的引用和转载必须通知作者:zphillm@hotmail.com

转载于:https://www.cnblogs.com/ZphillM/archive/2005/08/06/208713.html

深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值