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
C# Source Code Implementation for RVJ.Core.Person == and != Operators
MSIL Implementation of the RVJ.Core.Business.Person.op_Equality( RVJ.Core.Business.Person, RVJ.Core.Business.Person ) Method
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.
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
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.
Public API for Accessing the System.GetType() Instance Method
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().
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.
Default Constructor Generated Automatically by the Compiler, and type Implicitly Derives from the System.Object Root Type
Default Constructor Generated Automatically by the Compiler, and type Explicitly Derives from the System.Object Root Type
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.
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 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.