CLR via C# 3rd - 08 - Methods

 

   Kinds of methods
 
     Constructors
     Type constructors
     Overload operators
     Type conversions (for   implicit  and   explicit  casting)
     Extension methods
     Partial methods.
    
     
  1. Instance Constructors and Classes (Reference Types)
 
     Constructor methods are always called   .ctor  (for   constructor) in a method definition metadata table.
 
     When constructing a reference type object, the memory allocated for the object is always zeroed out before the type’s instance constructor is called. Any fields that the constructor doesn’t explicitly overwrite are guaranteed to have a value of   0  or   null.
 
     Unlike other methods, instance   constructors  are   never inherited. That is, a class has only the instance constructors that the class itself defines. Since instance constructors are never inherited, you cannot apply the following modifiers to  an instance constructor:   virtual,   newoverride,   sealed, or   abstract. If you define a class that does not explicitly define any constructors, the C# compiler defines a   default (parameterless) constructor  for you whose implementation   simply   calls  the   base class’s parameterless constructor.
 
       For example, if you define the following class:
public class SomeType {
}
     it is as though you wrote the code like this:
public class SomeType {
public SomeType() : base() { }
}
 
     If the class is   abstract, the compiler-produced default constructor has   protected  accessibility; otherwise, the constructor is given   public  accessibility. If the base class doesn’t offer a parameterless constructor, the derived class must explicitly call a base class constructor or the compiler will issue an error. If the class is   static  ( sealed  and   abstract), the compiler will not emit a default constructor at all into the class definition.
 
     In a few situations, an instance of a type can be created without an instance constructor being called. In particular, calling   Object’s MemberwiseClone  method allocates memory, initializes the object’s overhead fields, and then copies the source object’s bytes to the new object. Also, a constructor is usually not called when deserializing an object with the runtime serializer. The deserialization code allocates memory for the object without calling a constructor using the   System.Runtime.Serialization.FormatterServices  type's  GetUninitializedObject  or   GetSafeUninitializedObject  methods.
 
      Important  You should not call any virtual methods within a constructor that can affect the object being constructed. The reason is if the virtual method is overridden in the type being instantiated, the derived type’s implementation of the overridden method will execute, but all of the fields in the hierarchy have not been fully initialized. Calling a virtual method would therefore result in unpredictable behavior.
 
     The C# compiler allows the convenient syntax that lets you initialize the instance fields inline and translates this to code in the constructor method to perform the initialization. This means that you should be aware of code explosion. After this initialization code, the compiler inserts a call to the base class’s constructor, and then the compiler appends to the method the code that appears in the constructor methods. Note that the fields without the convenient syntax will be guaranteed to be initialized even though no codes exists to explicitly initialized them.
 
      Note  The compiler initializes any fields using the convenient syntax before calling a base class’s constructor to maintain the impression that these fields always have a value as the source code appearance dictates. The potential problem occurs when a base class’s constructor invokes a virtual method that calls back into a method defined by the derived class. If this happens, the fields initialized using the convenient syntax have been initialized before the virtual method is called.
 
     If you have several initialized instance fields and a lot of overloaded constructor methods, you should consider defining the fields without the  initialization, creating a single constructor that performs the common initialization, and having each constructor explicitly call the common initialization constructor. This approach will reduce the size of the generated code.         
 
     internal sealed class SomeType {
// Do not explicitly initialize the fields here
private Int32 m_x;
private String m_s;
private Double m_d;
private Byte m_b;
 
// This constructor sets all fields to their default.
// All of the other constructors explicitly invoke this constructor.
public SomeType() {
m_x = 5;
m_s = "Hi there";
m_d = 3.14159;
m_b = 0xff;
}
// This constructor sets all fields to their default, then changes m_x.
public SomeType(Int32 x) : this() {
m_x = x;
}
// This constructor sets all fields to their default, then changes m_s.
public SomeType(String s) : this() {
m_s = s;
}
// This constructor sets all fields to their default, then changes m_x & m_s.
public SomeType(Int32 x, String s) : this() {
m_x = x;
m_s = s;
}
}
 
2. Instance Constructors and Structures (Value Types)
 
     The C# compiler   doesn't emit default parameterless constructors  for value types. 
 
     C#  doesn’t allow  a value type to define a   parameterless  constructor.
 
     The CLR does allow you to define constructors on value types. The only way that these constructors will execute is if you write code to   explicitly  call one of them.
 
     Without a parameterless constructor, a value type’s fields are always initialized to   0/null.
 
      Note  Strictly speaking, value type fields are guaranteed to be 0/null when the value type is a field nested within a reference type. However, stack-based value type fields are not guaranteed to be 0/null. For verifiability, any stack-based value type field must be written to prior to being read. If code could read a value type’s field prior to writing to the field, a security breach is possible. C# and other compilers that produce verifiable code ensure that all stack-based value types have their fields zeroed out or at least written to before being read so that a verification exception won’t be thrown at run time. For the most part, this means that you can assume that your value types have their fields initialized to 0, and you can completely ignore everything in this note.
 
     Because C# doesn’t allow value types with parameterless constructors, compiling the following type produces the following message: " error CS0573: 'SomeValType.m_x': cannot have instance field initializers in structs."
 
internal struct SomeValType {
      // You cannot do inline instance field initialization in a value type
     private Int32 m_x = 5;
}
 
     As an alternative way to initialize all the fields of a value type, you can actually do this:
 
// C# allows value types to have constructors that take parameters.
public SomeValType(Int32 x) {
// Looks strange but compiles fine and initializes all fields to 0/null
this = new SomeValType();
m_x = x; // Overwrite m_x’s 0 with x
// Notice that m_y was initialized to 0.
}
     In a value type’s constructor, this represents an instance of the value type itself and you can actually assign to it the result of   newing up an instance of the value type, which really   just zeroes out all the fields. In a reference type’s constructor, this is considered read-only and so you cannot assign to it at all.

3. Type Constructors
 
     In addition to instance constructors, the CLR also supports type constructors (also known as  static constructors, class constructors, or type initializers).
 
     A type constructor can be applied to interfaces (although C# doesn’t allow this), reference types, and value types.
 
     Type constructors are used to set the initial state of a type.
 
     By default, types don’t have a type constructor defined within them. If a type has a   type constructor, it can have   no more than one. In addition, type constructors   never have parameters.
 
     You’ll notice that you define type constructors just as you would parameterless instance constructors, except that you must mark them as   static. Also, type constructors should always be   private; C# makes them private for you automatically. In fact, if you explicitly mark a type constructor as   private  (or anything else) in your source code, the C# compiler issues the following error: " error CS0515: 'SomeValType.SomeValType()': access modifiers are not allowed on static constructors."
 
      Important While you can define a type constructor within a value type, you should never actually
do this because there are times when the CLR will not call a value type’s static type constructor.
Here is an example:
internal struct SomeValType {
     static SomeValType() {
          Console.WriteLine("This never gets displayed");
     }
     public Int32 m_x;
}
 
public sealed class Program {
     public static void Main() {
          SomeValType[] a = new SomeValType[10];
          a[0].m_x = 123;
          Console.WriteLine(a[0].m_x); // Displays 123
     }
}
 
      Note  Since the CLR guarantees that a type constructor executes only once per AppDomain and is thread-safe, a type constructor is a great place to initialize any   singleton  objects required by the type.
 
     Finally, if a type constructor throws an unhandled exception, the CLR considers the type to be unusable. Attempting to access any fields or methods of the type will cause a  System.TypeInitializationException  to be thrown.
 
     When the C# compiler generates IL code for the type constructor, it first emits the code required to initialize the static fields followed by the explicit code contained in your type constructor method.
 
      Important  Developers occasionally ask me if there’s a way to get some code to execute when a type is unloaded. You should first know that types are unloaded only when the AppDomain unloads. When the AppDomain unloads, the object that identifies the type becomes unreachable, and the garbage collector reclaims the type object’s memory. This behavior leads many developers to believe that they could add a static Finalize method to the type, which will automatically get called when the type is unloaded. Unfortunately, the CLR doesn’t support static Finalize methods. All is not lost, however. If you want some code to execute when an AppDomain unloads, you can register a callback method with the System.AppDomain type’s DomainUnload event.
 
4. Type Constructor Performance
 
     The JIT compiler determines whether it must emit a call to execute a type constructor into the method. If the JIT compiler decides to emit the call, it must decide where it should emit the call. There are two possibilities here:
 
     1) The JIT compiler can emit the call immediately before code that would create the first instance of the type or immediately before code that accesses a noninherited field or member of the class. This is called precise semantics because the CLR will call the type constructor at precisely the right time.
 
     2) The JIT compiler can emit the call sometime before code first accesses a static field or a static or instance method, or invokes an instance constructor. This is called before-fieldinit semantics because the CLR guarantees only that the static constructor will run some time before the member is accessed; it could run much earlier.
 
     When the C# compiler sees a class with static fields that use   inline initialization, the compiler emits the class’s type definition table entry with the   BeforeFieldInit  metadata flag.
 
     When the C# compiler sees a class with an   explicit type constructor, the compiler emits the class’s type definition table entry  without  the   BeforeFieldInit  metadata flag.
 
5. Operator Overload Methods
 
     The CLR specification mandates that operator overload methods be public and static methods. In addition, C# (and many other languages) requires that at least one of the operator method’s parameters must be the same as the type that the operator method is defined within.
 
     Here is an example of an operator overload method defined in a C# class definition:
 
public sealed class Complex {
      public static Complex operator+(Complex c1, Complex c2) { ... }
}
 
      The compiler emits a metadata method definition entry for a method called   op_Addition; the method definition entry also has the   specialname  flag set, indicating that this is a “special” method.
 
C# Unary Operators and Their CLS-Compliant Method Names
 
C# Binary Operators and Their CLS-Compliant Method Names
 
6. Conversion Operator Methods
 
     Conversion operators are methods that convert an object from one type to another type. You define a conversion operator method by using special syntax. The CLR specification mandates that conversion overload methods be   public  and   static  methods
 
           public   sealed   class   Rational
    {
          // Constructs a Rational from an Int32
          public   Rational( Int32   num) { }
 
          // Constructs a Rational from a Single
          public   Rational( Single   num) { }
 
          // Convert a Rational to an Int32
          public   Int32   ToInt32() {   return   Int32 .MinValue; }
 
          // Convert a Rational to a Single
          public   Single   ToSingle() {   return   Single .MinValue; }
 
          // Implicitly constructs and returns a Rational from an Int32
          public   static   implicit   operator   Rational   ( Int32   num)
        {
              return   new   Rational   (num);
        }
 
          // Implicitly constructs and returns a Rational from a Single
          public   static   implicit   operator   Rational   ( Single   num)
        {
              return   new   Rational   (num);
        }
 
          // Explicitly returns an Int32 from a Rational
          public   static   explicit   operator   Int32   ( Rational   r)
        {
              return   r.ToInt32();
        }
 
          //Explicitly returns a Single from a Rational
          public   static   explicit   operator   Single   ( Rational   r)
        {
              return   r.ToSingle();
        }
    }
 
     
     public   sealed   class   Program
     {
           public   static   void   Main()
        {
               Rational   r1 = 5;           // Implicit cast from Int32 to Rational
               Rational   r2 = 2.5F;       // Implicit cast from Single to Rational
     
               Int32   x = ( Int32   )r1;      // Explicit cast from Rational to Int32
               Single   s = ( Single   )r2;     // Explicit cast from Rational to Single
        }
     }
 
     Under the covers, the C# compiler detects the casts (type conversions) in the code and internally generates IL code that calls the conversion operator methods defined by the Rational type. 
 
     For the Rational type, the metadata for the four conversion operator methods looks like this:
public static Rational op_Implicit(Int32 num)
public static Rational op_Implicit(Single num)
public static Int32     op_Explicit(Rational r)
public static Single   op_Explicit(Rational r)
 
     As you can see, methods that convert an object from one type to another are always named  op_Implicit  or   op_Explicit.
 
      Note  The two op_Explicit methods take the same parameter, a Rational. However, the methods differ by their return value, an Int32 and a Single. This is an example of two methods that differ only by their return type. The CLR fully supports the ability for a type to define multiple methods that differ only by return type. However, very few languages expose this ability. As you’re probably aware, C++, C#, Visual Basic, and Java are all examples of languages that don’t support the definition of multiple methods that differ only by their return type.
 
      Note  C# generates code to invoke explicit conversion operators when using a cast expression; they are never invoked when using C#’s as or is operators.
 
7. Extension Methods
 
     To turn method into an extension method, we simply add the   this  keyword before the first argument.
 
public static class StringBuilderExtensions {
public static Int32 IndexOf(this StringBuilder sb, Char value) {
     for (Int32 index = 0; index < sb.Length; index++)
          if (sb[index] == value) return index;
     return -1;
}
}
 
     Now, when the compiler sees code like this:
         
          Int32 index = sb.IndexOf('X');
 
     There are some additional rules and guidelines that you should know about extension methods.
 
     1) C# supports extension methods only; it does not offer extension properties, extension events, extension operators, and so on.
 
     2) Extension methods (methods with this before their first argument) must be declared in non-generic, static classes. However, there is no restriction on the name of the class; you can call it whatever you want. Of course, an extension method must have at least one parameter, and only the first parameter can be marked with the this keyword.
 
     3) The C# compiler looks only for extension methods defined in static classes that are themselves defined at the file scope. In other words, if you define the static class nested within another class, the C# compiler will emit the following message: " error CS1109: Extension method must be defined in a top-level static class; StringBuilderExtensions is a nested class."
 
     4) Since the static classes can have any name you want, it takes the C# compiler time to find extension methods as it must look at all the file-scope static classes and scan their static methods for a match. To improve performance and also to avoid considering an extension method that you may not want, the C# compiler requires that you “import” extension methods. For example, if someone has defined a StringBuilderExtensions class in a Wintellect namespace, then a programmer who wants to have access to this class’s extension methods must put a using Wintellect; directive at the top of his or her source code file.
 
     5) It is possible that multiple static classes could define the same extension method. If the compiler detects that two or more extension methods exist, then the compiler issues the following message: " error CS0121: The call is ambiguous  between the following methods or properties: 'StringBuilderExtensions. IndexOf(string, char)' and 'AnotherStringBuilderExtensions. IndexOf(string, char)'." To fix this error, you must modify your source code. Specifically, you cannot use the instance method syntax to call this static method anymore; instead you must now use the static method syntax where you explicitly indicate the name of the static class to explicitly tell the compiler which method you want to invoke.
 
     6) You should use this feature sparingly, as not all programmers are familiar with it. For example, when you extend a type with an extension method, you are actually extending derived types with this method as well. Therefore, you should not define an extension method whose first parameter is   System.Object, as this method will be callable for all expression types and this will really pollute Visual Studio’s IntelliSense window.      
 
     7) There is a potential versioning problem that exists with extension methods. If, in the future, Microsoft adds an   IndexOf  instance method to their   StringBuilder  class with the same prototype as my code is attempting to call, then when I recompile my code, the compiler will bind to Microsoft’s   IndexOf  instance method instead of my static  IndexOf  method. Because of this, my program will experience different behavior. This versioning problem is another reason why this feature should be used sparingly.
 
     The Extension Attribute
 
     In C#, when you mark a static method’s first parameter with the this keyword, the compiler internally applies a custom attribute to the method and this attribute is persisted in the resulting file’s metadata. The attribute is defined in the System.Core.dll assembly, and it looks like this:
 
// Defined in the System.Runtime.CompilerServices namespace
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]
public sealed class ExtensionAttribute : Attribute {
}
 
     In addition, this attribute is applied to the metadata for any static class that contains at least one extension method. And this attribute is also applied to the metadata for any assembly that contains at least one static class that contains an extension method. So now, when compiling code that invokes an instance method that doesn’t exist, the compiler can quickly scan all the referenced assemblies to know which ones contain extension methods. Then it can scan only these assemblies for static classes that contain extension methods, and it can scan just the extension methods for potential matches to compile the code as quickly as possible.
 
7. Partial Methods
 
 
     Imagine that you use a tool that produces a C# source code file containing a type definition.
 
// Tool-produced code in some source code file:
internal class Base {
private String m_name;
// Called before changing the m_name field
protected   virtual void OnNameChanging(String value) {
          }
 
public String Name {
     get { return m_name; }
     set {
          OnNameChanging(value.ToUpper()); // Inform class of potential change
          m_name = value; // Change the field
     }
          }
}
 
// Developer-produced code in some other source code file:
internal class   Derived : Base {
     protected   override void OnNameChanging(string value) {
          if (String.IsNullOrEmpty(value))
               throw new ArgumentNullException("value");
     }
}
 
     Unfortunately, there are two problems with the code above:
     1) The type must be a class that is not sealed. You cannot use this technique for sealed classes or for value types (because value types are implicitly sealed). In addition, you cannot use this technique for static methods since they cannot be overridden.
 
     2) There are efficiency problems here. A type is being defined just to override a method; this wastes a small amount of system resources. And, even if you do not want to override the behavior of OnNameChanging, the base class code still invokes a virtual method which simply does nothing but return. Also, ToUpper is called whether OnNameChanging accesses the argument passed to it or not.
 
     C#’s partial methods feature allows you the option of overriding the behavior or a type while fixing the aforementioned problems.
 
// Tool-produced code in some source code file:
internal sealed   partial class Base {
private String m_name;
// This defining-partial-method-declaration is called before changing the m_name field
partial void OnNameChanging(String value);
public String Name {
get { return m_name; }
set {
OnNameChanging(value.ToUpper()); // Inform class of potential change
m_name = value; // Change the field
}
}
}
 
// Developer-produced code in some other source code file:
internal sealed   partial class Base {
// This implementing-partial-method-declaration is called before m_name is changed
partial void OnNameChanging(String value) {
if (String.IsNullOrEmpty(value))
throw new ArgumentNullException("value");
}
}
 
     There are several things to notice about this new version of the code:
     1) The class is now sealed (although it doesn’t have to be). In fact, the class could be a static class or even a value type.
     
     2) The tool-produced code and the developer-produced code are really two partial definitions that ultimately make up one type definition.
 
     3) The tool-produced code defined a partial method declaration. This method is marked with the   partial token and it has no body.
 
     4) The developer-produced code implemented the partial method declaration. This method is also marked with the   partial token and it has a body.
 
     Again, the big benefit here is that you can rerun the tool and produce new code in a new source code file, but your code remains in a separate file and is unaffected. And, this technique works for   sealed classes,   static classes, and   value types.
 
     But, there is another big improvement we get with partial methods. Let’s say that you do not need to modify the behavior of the tool-produced type. In this case, you do not supply your source code file at all. If you just compile the tool-produced code by itself, the compiler produces IL code and metadata as if the tool-produced code looked like this:
 
// Logical equivalent of tool-produced code if there is no
// implementing partial method declaration:
internal sealed class Base {
private String m_name;
 
public String Name {
get { return m_name; }
set {
     m_name = value; // Change the field
}
}
}
 
      Note If there is no implementing partial method declaration, the compiler will not emit any metadata representing the partial method. In addition, the compiler will not emit any IL instructions to call the partial method. And the compiler will not emit code that evaluates any arguments that would have been passed to the partial method. In this example, the compiler will not emit code to call the ToUpper method. The result is that there is less metadata/IL, and the runtime performance is awesome.
 
     Rules and Guidelines
     1) They can only be declared within a partial class or struct.
 
     2) Partial methods must always have a return type of   void, and they cannot have any parameters marked with the   out modifier. These restrictions are in place because at runtime, the method may not exist and so you can’t initialize a variable to what the method might return because the method might not exist. Similarly, you can’t have an out parameter because the method would have to initialize it and the method might not exist. A partial method may have   ref parameters, may be generic, may be instance or static, and may be marked as   unsafe.
 
     3) Of course, the defining partial method declaration and the implementing partial method declaration must have   identical signatures. If both have custom   attributes applied to them, then the compiler combines both methods’ attributes together. Any attributes applied to a parameter are also combined.
 
     4) If there is no implementing partial method declaration, then you cannot have any code that attempts to create a delegate that refers to the partial method. Again , the reason is that the method doesn’t exist at runtime. The compiler produces this message: " error CS0762: Cannot create delegate from method 'Base.OnNameChanging(string)' because it is a partial method without an implementing declaration".
     
     5) Partial methods are always considered to be   private methods. However, the C# compiler   forbids you from putting the   private keyword before the partial method declaration.

转载于:https://www.cnblogs.com/junier/p/3583808.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值