CLR via C# 3rd - 08 - Methods

孤街醉人 提交于 2020-02-12 06:15:15

 

   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.
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!