© Roger Villela 2020
R. VillelaPro .NET 5 Custom Librarieshttps://doi.org/10.1007/978-1-4842-6391-4_3

3. .NET Methods: Implementation Details

Roger Villela1  
(1)
Sao Paulo, São Paulo, Brazil
 

This chapter covers methods that a .NET custom data type inherits from the System.Object root .NET data type and aspects of the execution environment.

Methods

Previously in this book, you read about methods that we should override for .NET custom data types. This section covers some internal aspects of inherited methods and related issues with regard to the execution environment.

About the Use of Operators

Operators are implemented as methods for implementing specific operations, and they are also implemented using the static keyword for modifier and the public keyword for access modifier (for example, when implementing the operators == and !=). Listing 3-1 shows an example of C# source code implementation, and Listing 3-2 and Listing 3-3 show the Microsoft Intermediate Language (MSIL) source code generated from a C# source code implementation, respectively:
                            public static Boolean operator ==( Person first, Person second ) {
            return Person.ReferenceEquals( first, second );
        }
                             public static Boolean operator !=( Person first, Person second ) {
            return !Person.ReferenceEquals( first, second );
        }
Listing 3-1

C# Source Code Implementation for RVJ.Core.Person == and != Operators

.method public hidebysig specialname static
        bool  op_Equality(class RVJ.Core.Business.Person first,
                          class RVJ.Core.Business.Person second) cil managed
{
  // Code size       8 (0x8)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldarg.1
  IL_0002:  call       bool RVJ.Core.Business.Person::ReferenceEquals(object,
                                                             object)
  IL_0007:  ret
} // end of method Person::op_Equality
Listing 3-2

MSIL Implementation of the RVJ.Core.Business.Person.op_Equality( RVJ.Core.Business.Person, RVJ.Core.Business.Person ) Method

.method public hidebysig specialname static
        bool  op_Inequality(class RVJ.Core.Business.Person first,
                            class RVJ.Core.Business.Person second) cil managed
{
  // Code size       11 (0xb)
  .maxstack  8
ldarg.0
ldarg.1
call       bool RVJ.Core.Business.Person::ReferenceEquals( object,  object )
ldc.i4.0
ceq
ret
} // end of method Person::op_Inequality
Listing 3-3

MSIL Implementation of the RVJ.Core.Business.Person.op_Inequality( RVJ.Core.Business.Person, RVJ.Core.Business.Person ) Method

In MSIL implementations of equality (==) and inequality (!=) operators and operations, we have the hidebysig and specialname MSIL keywords as part of the metadata.

The keyword hidebysig means “hide by signature” and is ignored by the implementation of the Virtual Execution System (VES). In ECMA-335, however, this is defined as supplied only for the use of tools such as compilers, syntax analyzers in programming languages, and code generators.

In programming language syntaxes and semantics, hidebysig defines that all declared/defined methods in a .NET custom data type must hide all inherited .NET methods that have a matching method signature, and this is valid for any point in the hierarchy of base class types.

When hidebysig is omitted in the metadata of the MSIL for the method, the rules in programming languages must hide all methods with the same name, and do not consider the signature for this scenario as a differential.

Typically, the C++ programming language follows “hide by name” as the semantics for this context, and C# and Java use both “hide by name” and “hide by signature” for semantics.

When present or not in MSIL metadata, the interpretation of this scenario for keyword hidebysig is part of programming language semantics, syntax, and specialized tools. At the time of this writing, the execution environment provided by VES ignores this keyword.

For the MSIL specialname keyword , as indicated by its name, the method needs a different and specialized treatment by specialized tools, such as compilers, metadata-validation tools, and reflection-based libraries.

This differs for the MSIL keyword rtspecialname (which means “runtime special name”) that is applied for a metadata item when the VES-provided execution environment needs to provide a different and specialized treatment for the MSIL metadata item. This is the case with, for example, MSIL keywords .ctor and .cctor (constructor and class constructor, respectively).

Operator overloading is described through method names and setting the MSIL specialname bit in the metadata. This combination helps to avoid name collision between items generated by tools and the execution environment spaces versus items defined/informed through the developer’s spaces.

Operator Overloading: Programming Language Semantics and Syntaxes

When operator overloading is supported by a programming language’s semantics and syntaxes , and the described semantics above are also supported, the ECMA-335 specifies precise semantics for the work of operators, including the name for operator methods.

The required prefix op_ is used as part of the name of the methods for the operators (for example, op_Equality() and op_Inequality()).

The full names of operator methods are also special and defined in ECMA-335.

The ECMA-335 specification includes an “intermediate assembly language”: the MSIL. Here is a necessary distinction. When we write code using a programming language that adheres to the ECMA-335 specification, the result of the compiled code is a sequence of instructions of an MSIL instruction set. These instructions are not for real hardware or processors. Instead, they are for a virtual environment that includes some characteristics and functionalities of the elements in a real computer (exactly what the resources in the ECMA-335 specification describe).

The virtual environment specializations are based on what an advanced operating system has (for example, advanced security rules, mechanisms to constantly observe your own environment, ways to guarantee data integrity based on more flexible or disciplined rules, capacity to recognize contextuality and to be dynamically extensible and expandable, interact with different and specialized environments like data management systems, development system, other platforms and capable of host, and be hosted by other environments.

Remember that this is not a one-to-one mapping between reserved words, data structures, specialized resources, or anything else in programming languages that support the .NET platform. That is, what is formalized through the instructions in MSIL, what is defined by the ECMA-335 specification, and what is implemented by the mechanisms on the platform is what prevails.

Remember that the .NET platform is programming language, operating system, and hardware platform agnostic. So, not every operator is supported by every programming language for the platform. Consult the programming language documentation for details about supported operators.

The following list shows examples of required names for binary operators. When a compiler tool chain generates the MSIL code, the following names should be used:
  • op_Addition for + symbol

  • op_Subtraction for - symbol

  • op_Multiply for * symbol

  • op_Division for / symbol

  • op_Modulus for % symbol

  • op_ExclusiveOr for ^ symbol

  • op_BitwiseAnd for & symbol

  • op_BitwiseOr for | symbol

  • op_LogicalAnd for && symbols

  • op_LogicalOr for || symbols

  • op_Assign for = symbol

  • op_LeftShift for << symbols

  • op_RightShift for >> symbols

  • op_Equality for == symbols

  • op_GreaterThan for > symbol

  • op_LessThan for < symbol

  • op_Inequality for != symbols

  • op_GreaterThanOrEqual for >= symbols

  • op_LessThanOrEqual for <= symbols

  • op_MemberSelection for -> symbols

  • op_RightShiftAssignment for >>= symbols

  • op_MultiplicationAssignment for *= symbols

  • op_PointerToMemberSelection for ->* symbols

  • op_SubtractionAssignment for -= for symbols

  • op_ExclusiveOrAssignment for ^= symbols

  • op_LeftShiftAssignment for <<= symbols

  • op_ModulusAssignment for %= symbols

  • op_AdditionAssignment for += symbols

  • op_BitwiseAndAssignment for &= symbols

  • op_BitwiseOrAssignment for |= symbols

  • op_Comma for, symbol

  • op_DivisionAssignment for /= symbols

For unary operators, the names must be as follows:
  • op_Decrement

  • op_Increment

  • op_UnaryNegation

  • op_UnaryPlus

  • op_LogicalNot

  • op_True

  • op_False

  • op_AddressOf

  • op_OnesComplement

  • op_PointerDereference

Be aware that the .NET environment uses the full sequence for the name of a .NET type. For example, the .NET execution environment does not have the concept of namespaces. That is an element of productivity made available by tools of programming languages and with support of professional integrated development environments (IDEs), analyzers, and source code editors.

In our example, this means that the name of our sample custom .NET data type is RVJ.Core.Business.Person() and not Person() only. In fact, the set of data information used to distinguish a .NET class type has more than a sequence of names.

In the intermediate language generated, the source code is based on the elements defined in the ECMA-335 and some extensions, depending on the provider of the .NET environment implementation and features supported.

For our examples, we are using the Microsoft implementation of Common Intermediate Language (CIL), known as MSIL, but with all the features available in all supported platforms, such as Microsoft Windows, Linux distributions, and the Apple macOS.

Working with System.Object.GetType()

This section covers methods that we do not need to override in our .NET custom data types, using the System.Object.GetType() instance method as an example.

System.Object.GetType() is a noninheritable instance method defined with the public access modifier keyword, and it is nonvirtual. In that way, any instance of a .NET reference type or .NET value type can access this instance method.

Listing 3-4 shows that we have the public API for System.GetType() that is available in .NET Base Class Library (BCL).
public System.Type GetType();
Listing 3-4

Public API for Accessing the System.GetType() Instance Method

For System.Object, we have the declaration shown in Listing 3-5 for the System.Object.GetType() instance method (as-is in BCL managed source code at the time of this writing).
[Intrinsic]
[MethodImpl(MethodImplOptions.InternalCall]
public extern Type GetType();
Listing 3-5

System.Object.GetType() Instance Method (As-Is in BCL Managed Source Code for .NET)

This means that the implementation of System.Object.GetType() is provided in the C++ programming language portion of a CLR implementation, and the method is specialized, requiring an implementation provided directly by the VES in runtime, and not as ordinary C# programming language source code using a public interface of foundational BCL.

In the implementation of native portions of foundational BCL, this method does not call System.Reflection (or similar) APIs. For example, a call to the System.Object.GetType() instance method, internally calls a C++ function (method) member called ObjectNative::GetClass() that calls ObjectNative::GetClassHelper().

C++ function (method) member ObjectNative::GetClass() checks whether the type of object exists in a managed environment, as checked by the macros shown in Listing 3-6.
#define ObjectToOBJECTREF(obj)    ((PTR_Object) (obj))
#define OBJECTREFToObject(objref) ((PTR_Object) (objref))
Listing 3-6

Verifies Whether the Type of Object Exists in the Managed Environment and Is Valid for the VES

Be aware and do not confuse an unmanaged .NET type with a native .NET type, because the VES has support for both.

System.Object is a class type (and thus a reference type), a managed type, and a key piece of the foundational BCL library, framework class libraries such as the Framework Class Library (FCL), and extended/specialized libraries.

For example, we have the System.IntPtr and System.UIntPtr, which are unmanaged .NET types (and not native .NET types).

Resuming, our .NET custom data type does not need to create a new implementation for System.Object.GetType().

Constructors in a .NET Data Type

A reference type always has a default constructor , which is automatically generated by the compiler of the programming language that we are using; the CLR implementation model requires this.

Listing 3-7, Listing 3-8, and Listing 3-9 show three different definitions that have the same MSIL results and semantics.
namespace RVJ.Core.Business {
    public class Person {}
}
Listing 3-7

Default Constructor Generated Automatically by the Compiler, and type Implicitly Derives from the System.Object Root Type

namespace RVJ.Core.Business {
    public class Person : System.Object {}
}
Listing 3-8

Default Constructor Generated Automatically by the Compiler, and type Explicitly Derives from the System.Object Root Type

namespace RVJ.Core.Business {
    public class Person : System.Object {
               public Person() : base() {} // default instance constructor.
    }
}
Listing 3-9

Default Constructor Implemented by the Developer, Calls Directly the base Type in the Hierarchy, and type Explicitly Derives from the System.Object Root Type

A specific MSIL keyword rtspecialname is applied for a metadata item when the execution environment provided by VES needs to provide a different and specialized treatment for the MSIL metadata item and respective data structures and data types. Listing 3-10 and Listing 3-11 show cases when we have MSIL keywords .ctor / .cctor (for instance, constructor and class constructor, respectively).

Listing 3-10 shows excerpts of MSIL for the RVJ.Core.Business.Person instance constructor of C# as shown in Listing 3-7, Listing 3-8, and Listing 3-9.

We can see that the name of the method for the instance constructor is .ctor and not the name of the .NET type, (RVJ.Core.Business.Person() in our example).

Through the MSIL source code body of an instance constructor, the default constructor of the direct .NET base type is called, also using the name .ctor.

The MSIL .ctor method means “instance constructor.”
.class public auto ansi beforefieldinit RVJ.Core.Business.Person
       extends [System.Runtime]System.Object {
.method public hidebysig specialname rtspecialname
        instance void  .ctor() cil managed {
  // Code size       7 (0x7)
  .maxstack  8
  ldarg.0
  call       instance void [System.Runtime]System.Object::.ctor()
  ret
} // end of method Person::.ctor
} // end of class RVJ.Core.Business.Person
Listing 3-10

Instance Default Constructor, Uses .ctor for Identification and Uses Flag rtspecialname for Special Runtime Treatment by VES

Being a static method, the class constructor, as you can see in Listing 3-11, is using a similar pattern of instance constructors. The method for the class constructor is using .cctor and not the name of the .NET type (RVJ.Core.Business.Person() in our example).

The MSIL .cctor method means “class constructor.”
.class public auto ansi beforefieldinit RVJ.Core.Business.Person
       extends [System.Runtime]System.Object {
.method private hidebysig specialname rtspecialname static
        void  .cctor() cil managed {
  // Code size       1 (0x1)
  .maxstack  8
ret
} // end of method Person::.cctor
}
Listing 3-11

The Class Constructor Is Called Before the Instance Fields Are Initialized and Instance Methods Can Be Called

Constructors are important kinds of methods that we should always keep in mind for use in our .NET custom data types.

Class constructors are used by MSIL instructions for what is described in CLR jargon as “before field init” semantics.

We should override for a class that uses unmanaged resources, such as file handles or database connections that must be released when the managed object that uses them is discarded during garbage-collection automatic memory-management mechanisms.

Because the garbage collector releases managed resources automatically, we should not implement a System.Object.Finalize() method for managed objects.

In fact, the System.Object.Finalize() method does nothing by default, but we should override System.Object.Finalize() only if necessary, and only to release unmanaged resources. Reclaiming memory tends to take much longer if a finalization operation runs because such reclamation requires at least two garbage collections.

In addition, we should override the System.Object.Finalize() method for reference types only. The CLR finalizes reference types only. It ignores finalizers on value types, at least at the time of this writing.

The scope of the System.Object.Finalize() method is protected. We should maintain this limited scope when overriding the method in our .NET custom class type.

By keeping a System.Object.Finalize() instance protected method, we prevent users of our application from calling an System.Object.Finalize() method directly.

Every implementation of System.Object.Finalize() in a derived type must call its base type implementation of System.Object.Finalize(), and this is the only case in which application code is allowed to call System.Object.Finalize() directly.

An object’s Finalize method should not call a method on any objects other than that of its base class. This is because the other objects being called could be collected at the same time as the calling object, such as in the case of a CLR shutdown.

You will learn more about constructors, VES details, and finalizers in the upcoming chapters.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.67.251