.NET Book Zero Reading Notes (Learn C# from a C++ user perspective)


Interface looks like Class or Struct, but there is no body inside the method, such as:

// Interfaces are entirely overhead! They contain no code.
public interface IComparable
	int CompareTo(object obj);

Interfaces in the.NET Framework all start with I, but this is just a naming convention. There can also be Property in Interfaces, because Property is actually a compiler-generated function, but there can be no Body.

Note that a class can inherit only one class, but can inherit multiple Interface s

For example, by implementing the IComparable interface, the static function Array.Sort can sort objects of this class, such as the following:

partial class SuperDate: ExtendedDate, IComparable
	public int CompareTo(object obj)
		if (obj == null)
			return 1;
		if (!(obj is SuperDate))
			throw new ArgumentException();
		return this - (SuperDate)obj;

Then you can sort it using the Array.Sort function:


Note that Array.Sort also accepts the same Size array with two parameters. The first element of the first array and the second element of the second array form a key-value mapping relationship. When sorting the first array, the second array also changes with the change of the key, making it easier to write, for example:

using System;

class DateSorting
    static void Main()
        string[] strComposers =
            "John Adams", "Johann Sebastian Bach",
            "Bela Bartok", "Ludwig van Beethoven",
            "Hector Berlioz", "Pierre Boulez",
            "Johannes Brahms", "Benjamin Britten",
            "Aaron Copland", "Claude Debussy",
            "Philip Glass", "George Frideric Handel",
            "Franz Joseph Haydn", "Gustav Mahler",
            "Claudio Monteverdi", "Wolfgang Amadeus Mozart",
            "Sergei Prokofiev", "Steve Reich",
            "Franz Schubert", "Igor Stravinsky",
            "Richard Wagner", "Anton Webern"
        SuperDate[] sdBirthDates =
            new SuperDate(1947, 2, 15), new SuperDate(1685, 3, 21),
            new SuperDate(1881, 3, 25), new SuperDate(1770, 12, 17),
            new SuperDate(1803, 12, 11), new SuperDate(1925, 3, 26),
            new SuperDate(1833, 5, 7), new SuperDate(1913, 11, 22),
            new SuperDate(1900, 11, 14), new SuperDate(1862, 8, 22),
            new SuperDate(1937, 1, 31), new SuperDate(1685, 2, 23),
            new SuperDate(1732, 3, 31), new SuperDate(1860, 7, 7),
            new SuperDate(1567, 5, 15), new SuperDate(1756, 1, 27),
            new SuperDate(1891, 4, 23), new SuperDate(1936, 10, 3),
            new SuperDate(1797, 1, 31), new SuperDate(1882, 6, 17),
            new SuperDate(1813, 5, 22), new SuperDate(1883, 12, 3)
        Array.Sort(sdBirthDates, strComposers);
        for (int i = 0; i < strComposers.Length; i++)
            Console.WriteLine("{0} was born on {1}.",
            strComposers[i], sdBirthDates[i]);

The final output is sorted by birth date:

Claudio Monteverdi was born on 15 May 1567.
George Frideric Handel was born on 23 Feb 1685.
Johann Sebastian Bach was born on 21 Mar 1685.
Franz Joseph Haydn was born on 31 Mar 1732.
Wolfgang Amadeus Mozart was born on 27 Jan 1756.
Ludwig van Beethoven was born on 17 Dec 1770.
Franz Schubert was born on 31 Jan 1797.
Hector Berlioz was born on 11 Dec 1803.
Richard Wagner was born on 22 May 1813.
Johannes Brahms was born on 7 May 1833.
Gustav Mahler was born on 7 Jul 1860.
Claude Debussy was born on 22 Aug 1862.
Bela Bartok was born on 25 Mar 1881.
Igor Stravinsky was born on 17 Jun 1882.
Anton Webern was born on 3 Dec 1883.
Sergei Prokofiev was born on 23 Apr 1891.
Aaron Copland was born on 14 Nov 1900.
Benjamin Britten was born on 22 Nov 1913.
Pierre Boulez was born on 26 Mar 1925.
Steve Reich was born on 3 Oct 1936.
Philip Glass was born on 31 Jan 1937.
John Adams was born on 15 Feb 1947.


Interoperability means interactivity. This chapter focuses on how C#interacts with system API s and DLL s in other languages.

For example, here the system's WIN32 API is invoked to get the current time (although DataTime.Now for.NET provides this capability), /the common C#APIs for interaction are all under this namespace:

using System.Runtime.InteropServices;

The function signature C style of the WIN32 API called here is as follows:

// LPSYSTEMTIME is a pointer to SYSTEMTIME type
void GetSystemTime(LPSYSTEMTIME lpSystemTime);

typedef struct _SYSTEMTIME
	WORD wYear;// WORD is a 16-bit integer
	WORD wMonth;
	WORD wDayOfWeek;
	WORD wDay;
	WORD wHour;
	WORD wMinute;
	WORD wSecond;
	WORD wMilliseconds;

If you want to call this function on the side of C#, you need to create a pointer of the corresponding type, which defines a corresponding class in C#here:

using System.Runtime.InteropServices;

// StructLayoutAttribute is a class in a namespace that describes how a class or Struct's Fields should be interpreted
[StructLayout(LayoutKind.Sequential)]// This is called attribute
class SystemTime// Class names are slightly different. Note that class is not a struct, but it can also be a struct, but the method of transmission is slightly different.
	public ushort wYear;// Same name, byte length and access level
	public ushort wMonth;
	public ushort wDayOfWeek;
	public ushort wDay;
	public ushort wHour;
 	public ushort wMinute;
	public ushort wSecond;
	public ushort wMilliseconds;

In addition to using LayoutKind.Sequantial, you can also use LayoutKind.Explicit to specify byte-aligned lengths (give byte offsets for all the fields.) for all fields.

In addition, if you want to call a function of the system, you need to include it as a function Import of the dll, as follows:

// The attribute indicates the dynamic link library in which the function is stored.
static extern void GetSystemTime(SystemTime st);

That's the final code. Interestingly, although the API of the system requires a pointer to be passed in, the function here directly passes in a reference to the class object, so that means the function reference in C#is essentially a pointer?

using System;
using System.Runtime.InteropServices;
partial class SuperDate
    class SystemTime
        public ushort wYear;
        public ushort wMonth;
        public ushort wDayOfWeek;
        public ushort wDay;
        public ushort wHour;
        public ushort wMinute;
        public ushort wSecond;
        public ushort wMilliseconds;
    static extern void GetSystemTime(SystemTime st);
    public static SuperDate Today()
        SystemTime systime = new SystemTime();
        return new SuperDate(systime.wYear, systime.wMonth, systime.wDay);

Using C#struct object as a parameter to the system API
Why the class object used before can be passed in directly, but struct can't because the former is a reference when passed in, which can be considered a pointer here, but the latter is a value passed in, so if you want to pass in a struct, you must pass in its reference, then add ref or out keyword to it, because the out keyword is passed in,Object implementations that don't need to be passed are initialized, so adding an out here won't make it particularly clear why ref doesn't work, whether control can't be handed over like this, or what.
All in all, that's it:

SystemTime systime;
GetSystemTime(out systime);


Attributes are information you can attach to a type or member of a type. The information is stored as metadata along
with the compiled code.

Attribute s can be added to a Type or a member corresponding to a Type and are stored together as metadata after compilation

There may be objects of type PInvoke in the API of WIN32, which is described on this website:

Dates and Times

There's nothing to say in this chapter. It mainly introduces the use of a time class DateTime in the.NET Framework. The code example is as follows:

// August 29, 2007 15:30:00
DateTime dt = new DateTime(2007, 8, 29, 15, 30, 0);

// Getting all kinds of time information
DateTime dtLocal = DateTime.Now;
DateTime dateToday = DateTime.Today;
DateTime dtUtc = DateTime.UtcNow;
// There are also Local, Utc, and Unspecified time types
DateTime dtLocal = new DateTime(2007, 8, 29, 15, 30, 0, DateTimeKind.Local);

// There is also a TimeSpan class for calculating time differences
TimeSpan ts = dt1 – dt2;
TimeSpan ts = new TimeSpan(40, 30, 20, 10);
TimeSpan ts = new TimeSpan(4000, –3000, –2000, 1000);

Finally, different Calendar time recording methods are described, which seems quite silent:

// So many types in total

new DateTime(1900, 2, 29);//The default is Gregorian calendar, 1900 is not a leap year, errors will be reported
new DateTime(1900, 2, 29, new JulianCalendar());
new DateTime(5762, 5, 20, new HebrewCalendar());

Events and Delegates

The content of this chapter is mentioned in the reading notes, so don't mention it anymore

Files and Streams

C#supports reading and writing binary, text and XML files by default.

C#provides two namespaces to help read and write files:

  • System.IO: Can be used to help read and write binary and txt files
  • System.Xml: Can be used to read and write xml files

Under the System.IO namespace, the classes corresponding to the different files are:

  • FileStream:corresponding bytes
  • StreamReader and StreamWriter: corresponding txt file
  • BinaryReader and BinaryWriter: corresponding bytes file

Definitions of file and stream

As mentioned in the book, files stored on disk with paths and names are called files, and once open ed for reading and writing, this file becomes a stream.Instead of explaining it with data streams, a Stream is something you can read and write on, but Stream is not limited to just things on files. It can be a piece of data that's transferred from the network, or it can be an area you've created in Moory.In a console, keyboard input and text output are streams

Stream class in C#

There is an abstract class Stream under System.IO in C#with the following inheritance relationships:

A stream is an object that lets you read bytes, write bytes, and seek to a specific location. But not all Streams support these operations at the same time. Stream has four properties:

  • CanRead: If readable, you can call ReadByte on a Stream object to read a single Byte, or call Read to read an array of Bytes
  • CanWrite: Same as above, you can call WriteByte and Write, The Flush method writes any buffered output to the stream. (Whatever Flush is also written)
  • CanSeek: For example, for file reads and writes on disk, you can use the Length property to get the length of the Stream, the Position property to set the current pos, or you can view the currently read POS.Both Length and Position are of long type and can use SeekEnd, SeekCurrent, and SeekBegin functions (much like C++)
  • CanTimeout: For example, Stream, ReadTimeout, and WriteTimeout attributes for network transmission can set timeout values, which should be offline.

A few additional notes:

  • SetLength function can be called if it is readable and Seek
  • You can use BeginRead, EndRead, BeginWrite, and EndWrite methods to read or write the stream asynchronously, but I don't know exactly what Stream means here
  • Close stream using Close method

As you can see from the diagram above, C#Stream is an abstract class with four derived subclasses:

  • BufferedStream
  • FileStream
  • MemoryStream
  • NetworkStream

First let's talk about the most commonly used Stream: FileStream

FileStream Class

FileStream inherits from the Stream class and can perform rudimentary IO operations. The code to create FileStream is as follows:

// When the build file fails, IOException or FileNotFoundException is thrown, so it's best to do it in a try block
FileStream fs = new FileStream(filePath, FileMode.XXX);

// A third parameter can also be specified
FileStream fs = new FileStream(filePath, FileMode.XXX, FileAccess.XXX);

FileMode enumeration parameters
When you create a FileStream, you can specify the file operations associated with creating the Stream through the FileMode l enumeration, which has six modes:

  • FileMode.CreateNew: Create a new file, make it Stream, and fail if the file for the path already exists
  • FileMode.Create: Create a file, make it Stream, and if the file for the path already exists, empty the original content
  • FileMode.Open: Open the specified file and change it to Stream. If the path file does not exist, it fails
  • FileMode.OpenOrCreate: Open the specified file and make it Stream. If the path file does not exist, create the corresponding file and make it Stream
  • FileMode.Truncate: Empty the contents of the specified file, change it to Stream, and fail if the file does not exist
  • FileMode.Append: If the file does not exist, a new file will be created and made Stream;If FileStream can Read files, it fails (that is, Append mode can only write files);Will change the file to Stream, then Seek to the end of the file

Note: Failure to create FileStream above will cause an exception to be thrown

FileAccess enumeration parameters
In addition to specifying Stream's operation on files, FileAccess enumeration allows you to specify Stream's access rights to files. FileAccess has three modes: read-write, read-only, and write-only:

  • FileAccess.Read: Fails for FileMode.CreateNew, FileMode.Create, FileMode.Truncate, or FileMode.Append.There are six FileModes, four of which involve writing (creating a file is also a write), so these modes fail if they are read-only
  • FileAccess.Write: Fail for read-only files
  • FileAccess.ReadWrite: For read-only files, fail for FileMode.Append (Append is a write-only operation)

The code is as follows:

// The default FileAccess is ReadWrite and can generally be written without
new FileStream(strFileName, FileMode.OpenOrCreate);

// Error, because Append is a write-only operation, the default FileAccess is incorrect
new FileStream(strFileName, FileMode.Append);

// Correct, normally only Append mode requires additional FileAccess specification
new FileStream(strFileName, FileMode.Append, FileAccess.Write);

FileShare enumeration parameters
Through this enumeration, when opening a file, share it with other processes in the following states:

  • FileShare.None: default
  • FileShare.Read: Read together
  • FileShare.Write: Can write together
  • FileShare.ReadWrite: Read and write together

When two File Stream s want to share files, they must both declare the specified FileShare enumeration parameters when creating FileStream.

The code is as follows:

// When Stream can only read files, it generally allows other files to read as well.
// Inside the FileStream class, methods from Lock and Unlock are used to protect shared files
new FileStream(strFileName, FileMode.Open, FileAccess.Read, FileShare.Read);

Proerty of FileStream

  • Both CanRead and CanWrite Property depend on FileAccess in the constructor
  • CanSeek is always true for open files
  • When CanSeek is true, Length and Position are valid, Length is read-only roperty, and Position is readable and writable, which can be used to set read-write locations (in byte), Length and Position, Both properties are of type long, which means allow file sizes of them up to 9 terates (9× 10^9 bytes).

Method for FileStream

  • public override long Seek (long offset, System.IO.SeekOrigin origin): The second parameter is an enumeration (Begin, Current, and End), which represents the source of offset, similar to C's fsee function
  • public override int ReadByte(): Reads a single Byte, converts it to int return, returns -1 if end is read, and adds 1 when Position is read
  • public override int Read (byte[] array, int offset, int count): Reads an array of bytes. The second parameter is the offeset passed into Buffer, while count is the number of bytes read. The returned int represents the number of bytes read into the buffer, because the capacity of the array is likely greater than the number of bytes actually read, and returns 0 if End has been reached.For example:
byte[] buffer = new byte[1000];
fs.Read(buffer, 0, buffer.Length);
  • As for Write and WriteByte, they are similar
  • Finally, don't forget to call Closemethod

Demo of FileStream

  • C++ differs from C# in how exe is passed on the command line. C++ needs to pass two parameters, one is the number of parameters, the other is the parameter char* array, and the first parameter is the path of exe. C# only needs to pass one parameter, string[] strArgs, or String array, if no parameters are entered.Enter the name of exe as an argument (should the first String array passed in be filename?)
  • Here is a 16-byte transfer method with the following code:
// Incoming byte array, count <= 16, buffer is an array of size 16
static string ComposeLine(long addr, byte[] buffer, int count)
	// X in X4 is represented in hexadecimal, 4 is represented in digit
	// str seems to print out the total address in front of it
	string str = String.Format("{0:X4}-{1:X4} ", (uint)addr / 65536, (ushort)addr);

	// Traverse 16 byte s
	for (int i = 0; i < 16; i++)
		// 8 bits per byte, so it can be converted to a two-digit 16-digit number
		str += (i < count) ? String.Format("{0:X2}", buffer[i]) : " ";
		// The 16 bytes are 32 hexadecimal digits, with a separator after the eighth in the middle-
		str += (i == 7 && count > 7) ? "-" : " ";
	str += " ";
	// Convert Byte one by one to Char and write to the result
	for (int i = 0; i < 16; i++)
		// Convert Byte to Char
		char ch = (i < count) ? Convert.ToChar(buffer[i]) : ' ';
		// Special Control Character Special Processing
		str += Char.IsControl(ch) ? "." : ch.ToString();
	return str;

Disadvantages of FileStream

  • In C++, you can read the data directly as a Byte array, and then take the addresses and convert them into pointers to the data you want.But C# can't do it, you still have to create your own data structure and get it from each of the transformed Bytes, so it's not as flexible as reading FileStream in C++.

Stream Reader and Stream Writer

StreamReader is C#for reading text files, and the related class inheritance relationships are shown in the following figure:

Note: Although these classes do not inherit from the Stream class, they are essentially implemented through the Stream class.

The Text file itself is simple, but because Unicode exists, it becomes complex.char and string data in C#are coded in Unicode by default.

Two Classes of Constructors for StreamWriter

// 1. First class, create StreamWriter by opening a file
// These constructors all open the corresponding files, and the underlying layer should have created FileStream as well.
// Appnd is true if you want to preserve the contents of the file;size of buffer;Encoding using UTF8 by default
new StreamWriter(string filename)
new StreamWriter(string filename, bool append)
new StreamWriter(string filename, bool append, Encoding enc)
new StreamWriter(string filename, bool append, Encoding enc, int size)

// 2. Category 2, creating StreamWriter from Stream
new StreamWriter(Stream strm)
new StreamWriter(Stream strm, Encoding enc)
new StreamWriter(Stream strm, Encoding enc, int size)

The default is to use UTF8 encoding, and Encoding is an enumeration:


Using Encoding.Unicode as an encoded file or Stream, the initial characters are 0XFF and 0XFE, or 0XFEFF, which is a tag.
Define in the Unicode standard as the byte order mark (BOM).Still, I don't know much, I haven't studied much (P234)

Example of StreamWriter

// Write the time each time a write is opened
StreamWriter sw = new StreamWriter("StreamWriterDemo.txt", true);
sw.WriteLine("You ran the StreamWriterDemo program on {0}", DateTime.Now);

Two Kinds of Constructors for StreamReader

// Here, detection is used to detect the Encoding method of the file, which is identified from two to three bytes in front of the file
// If detection is true and Encoding is entered, it will be detected first, and if it fails, it will be read in the input encoding
// For example, there is no BOM for both UTF7 and ASCII codes, and bytes are in the range 0x00 to 0x7F

// 1.
new StreamReader(string filename);
new StreamReader(string filename, Encoding enc);
new StreamReader(string filename, bool detect);
new StreamReader(string filename, Encoding enc, bool detect);
new StreamReader(string filename, Encoding enc, bool detect, int size);

// 2.
new StreamReader(Stream strm);
new StreamReader(Stream strm, Encoding enc);
new StreamReader(Stream strm, bool detect);
new StreamReader(Stream strm, Encoding enc, bool detect);
new StreamReader(Stream strm, Encoding enc, bool detect, int size);

StreamReader's method

  • Peek and Read functions for character-by-character reading
  • ReadLine for progressive reading
  • ReadToEnd reads directly to the end of the file

Binary FIle I/O

A file, either a text file or a Binary file, has a simple inheritance structure for the related classes:

Both constructors need to pass in a Stream object

There are 18 Write overload functions in BinaryWriter. Typically, double and float data are written in fixed length. For binary data of String type, it is judged by the 8 bits of the first byte. The last seven bits represent the number of characters in the string. The highest bit is used to indicate whether there are more characters in the string, let alone if there are more.Not difficult.


System namespace has classes and functions related to getting environment information

Get information about all disks on the device

// Represents C disk information
DriveInfo info = new DriveInfo("C");
// Get disk information on your computer
DriveInfo[] infos = DriveInfo.GetDrives();
// DriveInfo has a Property called DriveType, which is an enumeration, Removable, Fixed, and CDRom

The code is as follows:

DriveInfo[] infos = DriveInfo.GetDrives();
foreach (DriveInfo info in infos)
	Console.Write("{0} {1}, ", info.Name, info.DriveType);
	if (info.IsReady)
		Console.WriteLine("Label: {0}, Format: {1}, Size: {2:N0}", info.VolumeLabel, info.DriveFormat, info.TotalSize);
		Console.WriteLine("Not ready");

Print the following on the author's computer:

A:\ Removable, Not ready
C:\ Fixed, Label: Windows XP Pro, Format: NTFS, Size: 52,427,898,880
D:\ Fixed, Label: Available, Format: NTFS, Size: 52,427,898,880
E:\ Removable, Not ready
F:\ CDRom, Not ready
G:\ CDRom, Not ready
H:\ Fixed, Label: Windows Vista, Format: NTFS, Size: 32,570,863,616
// Is a USB Drive
I:\ Removable, Label: BOOKS, Format: FAT, Size: 1,041,989,632

Special Folders

// My Document Path

Path-dependent functions

  • Path.IsPathRooted tells you if the path name begins with a drive or
    a backslash.
  • Path.HasExtension tells you if the filename has an extension.
  • Path.GetFileName returns just the filename part of the file path.
  • Path.GetFileNameWithoutExtension returns the filename without
    the extension.
  • Path.GetExtension returns just the filename extension.
  • Path.GetDirectoryName returns just the directory path of the file
  • Path.GetFullPath possibly prepends the current drive and directory
    to the file path.
  • Path.GetPathRoot obtains the initial drive or backslash (if any).
  • Path.Combine
  • Path.ChangeExtension

File, FileInfo and Directory, DirectoryInfo
Essentially similar, File classes are all Static functions, and FileInfo needs to create Instance s to call them, both of which are essentially the same

This large chapter (Chapter 25) talks about a lot of file reading, file creation related operations, too boring to say more, it is not difficult, to check again in time.

String Theory

String in C# is an immutable object. It cannot change its length or the value of a single character inside it. It can only copy one and then change the copied object.In this case, a performance issue is raised when a String is modified more than once (such as adding a "u"The operation) will degrade performance, because the String's Deep Copy is constantly in progress at this time, and each modification will cause the memory on the heap that the original string points to to enter the GC.

Using StringBuilder, you can reduce copy consumption by repeatedly modifying strings, because StringBuilder can modify strings directly instead of modifying copied objects. When String memory is not enough, StringBuilder reallocates memory on the heap (which should be similar to a dynamic array in C++).The code is as follows:

StringBuilder builder = new StringBuilder();
for (int i = 0; i < 10000; i++)

string str = builder.ToString();// Use ToString to get the final result

In addition to using StringBuilder, you can also use the StringWriter mentioned in the previous chapter, which has the same performance and principle as StringBuilder:

StringWriter writer = new StringWriter();
for (int i = 0; i < iterations; i++)

string str = writer.ToString();

PS: But if I change the fifth character of a String 10,000 times in a row, I don't know if these classes can be used. If I understand them as dynamic arrays, I think they should be.


Finally, in the final chapter of the book, C#2.0 began to introduce generics, with syntax similar to C++ templates.

For example, if you want to design a 2D Point type, which has two properties, X and Y, X and Y should be reshaped for efficiency and Double for accuracy, then you may need to design two different classes according to different requirements. The code is as follows:

class IntegerPoint
	public int X;
	public int Y;
	public double DistanceTo(IntegerPoint pt)
		return Math.Sqrt((X - pt.X) * (X - pt.X) + (Y - pt.Y) * (Y - pt.Y));

class DoublePoint
	public double X;
	public double Y;
	public double DistanceTo(DoublePoint pt)
		return Math.Sqrt((X - pt.X) * (X - pt.X) + (Y - pt.Y) * (Y - pt.Y));

With generics, you can replace double s or int s with T, code like C++:

// The writing here is simpler and does not require template <typename T>to be written like C++.
// This T is a common notation, not necessarily T.
class Point<T>
	public T X;
	public T Y;
	public double DistanceTo(Point<T> pt)// The return types are both double and do not need to be changed
		// Note that this is incorrect because the X type is unknown and does not necessarily subtract from it. We will talk about it later
		// And even if the X type subtracts, Math.Sqrt accepts double, and the X type is not necessarily converted to a double type
		return Math.Sqrt((X - pt.X) * (X - pt.X) + (Y - pt.Y) * (Y - pt.Y));

// Use
Point<int> pti = new Point<int>();
pti.X = 26;
pti.Y = 14;

Point<double> ptd = new Point<double>();
ptd.X = 13.25;
ptd.Y = 3E-1;

It is worth noting that the generic function for DistanceTo is written incorrectly here, but should be correct in C++ because the C++ template's feature is simply a generation function, determined by the compiler, and will fail if not.C# uses one of the concepts in generics: Constraints

The keyword for Constrains is where, for example:

// Using type T on a Point, must inherit from SomeBaseClass
class Point<T> where T: SomeBaseClass

// The type T used on the Point must be a value type
class Point<T> where T: struct

// With type T on a Point, you must have a parameterless constructor
class Point<T> where T: new()

// Use to write multiple Contrains: Use type T on Point, must be a reference type, and must have a constructor with or without arguments
class Point<T> where T: class, new()

However, none of these Contraints meet the requirements of the DistanceTo function currently in use, where the requirements are:

  1. Type T must be subtractable
  2. Type T can be converted to a double type

The reality is that there is no way to write Contraints and T can support the subtraction operator.However, this can be achieved by letting T inherit an Interface.

Here you need to introduce an Interface in the System namespace, called IConvertible, from which Struct or Class, which inherits, needs to satisfy a series of functions to convert to basic types, including the ToDouble conversion function, so you can write as follows:

using System;
using System.Globalization;
class Point<T> where T:IConvertible

	NumberFormatInfo fmt = NumberFormatInfo.CurrentInfo;
	public double DistanceTo(Point<T> pt)
		// Here the fmt needs to be an object of a class that inherits the IFormatProvider interface
		return Math.Sqrt(Math.Pow(X.ToDouble(fmt) - pt.X.ToDouble(fmt), 2) + Math.Pow(Y.ToDouble(fmt) - pt.Y.ToDouble(fmt), 2));

By the way, IFormatProvider interface, which is an interface under the System namespace for format conversion, provides three derived classes of the interface under the System.Globalization namespace:

  • System.Globalization.CultureInfo: Format conversion for national culture
  • System.Globalization.DateTimeFormatInfo: Format conversion for dates
  • System.Globalization.NumberFormatInfo: Format conversion for numbers

The sample code is as follows:

using System;
using System.Globalization;

public class Example
   public static void Main()
      DateTime dateValue = new DateTime(2009, 6, 1, 16, 37, 0);
      CultureInfo[] cultures = { new CultureInfo("en-US"),
                                 new CultureInfo("fr-FR"),
                                 new CultureInfo("it-IT"),
                                 new CultureInfo("de-DE") };
      foreach (CultureInfo culture in cultures)
         Console.WriteLine("{0}: {1}", culture.Name, dateValue.ToString(culture));
// The example displays the following output:
//       en-US: 6/1/2009 4:37:00 PM
//       fr-FR: 01/06/2009 16:37:00
//       it-IT: 01/06/2009 16.37.00
//       de-DE: 01.06.2009 16:37:00

The complete code is as follows:

using System;
using System.Globalization;

class Point<T> where T : IConvertible
    public T X;
    public T Y;
    NumberFormatInfo fmt = NumberFormatInfo.CurrentInfo;
    // Notice how the constructor is written without parameters, default operator makes the value type bit 0 and the reference type null
    public Point()
        X = default(T);
        Y = default(T);

    // Two-Parameter Constructor
    public Point(T x, T y)
        X = x;
        Y = y;

    public double DistanceTo(Point<T> pt)
        return Math.Sqrt(Math.Pow(X.ToDouble(fmt) - pt.X.ToDouble(fmt), 2) +
        Math.Pow(Y.ToDouble(fmt) - pt.Y.ToDouble(fmt), 2));

Here is the code used:

class GenericPoints
    static void Main()
        // Points based on integers
        Point<int> pti1 = new Point<int>();
        Point<int> pti2 = new Point<int>(5, 3);
        // Points based on doubles
        Point<double> ptd1 = new Point<double>(13.5, 15);
        Point<double> ptd2 = new Point<double>(3.54, 5E-1);
        // Points based on strings
        // This is because the String class also inherits the IConvertible interface, where the ToDouble function calls Double.Parse
        Point<string> pts1 = new Point<string>("34", "27");
        Point<string> pts2 = new Point<string>("0", "0");
		// You can even write Point<DateTime>...

Finally, some generic containers are introduced, not to mention more:

Where generics had the biggest impact in the .NET Framework is with the System.Collections namespace. With .NET 2.0, that namespace has been largely superseded by the System.Collection.Generic namespace, which includes generic versions of Queue, Stack, Dictionary, SortedList, and List (which is the generic version of ArrayList). These versions provide type safety that the non-generic versions do not, and are now preferred for most applications.

Nullable Types

By default, Class in C#is nullable, while Struct's corresponding value type is not null.

But suppose there is such an application where you want a function that returns a Vector3, and if the calculation is wrong, it returns a null, meaning no result is returned.However, since Vector3 is a value type, returning null directly is an error.To solve this problem,.NET 2.0 introduced Nullable Types, which allow null to assign values to types:

Any value type—int, bool, DateTime, or any structure that you define—can be made into a "nullable"

In.NET 2.0, there are three changes to C#to support Nullablt Types:

  1. A Nullable generic structure was added to System's namespace
  2. C# needed to recognize nullable types in some cases.
  3. CLR needs to identify nullable types for boxing

Implementation of Nullable Types
The author speculates that the underlying code would look something like this:

// T is a value type, Nullable is also a value type, is actually a wrapper, contains target and hasValue (used to identify whether it is Null)
public struct Nullable<T> where T : struct // Pure supposition
    T value;
    bool hasValue;
    // A Constructor with parameters is defined, but note that the Nullable class also has a default constructor with a value type
    public Nullable(T value)
        this.value = value;
        hasValue = true;
    // Read-Only Properties
    public bool HasValue
        get { return hasValue; }
    public T Value
            if (!HasValue)
                throw new InvalidOperationException(
                "Nullable object must have a value");
            return value;

Write code like this when using:

Nullable<DateTime> ndt = new Nullable<DateTime>();
// Throw an exception: Nullable object must have a value

Nullable<DateTime> ndt = new Nullable<DateTime>(DateTime.Now);
// Can be normal Print

// If you want to know if ndt is null, you need to tell it by its hasValue

Conversion between Nullable Types and Value
You can add two more cast to the class:

public struct Nullable<T> where T : struct // Pure supposition
	// Provide implicit conversion of value to Nullable<T>
	public static implicit operator Nullable<T>(T value)
		return new Nullable<T>(value);
	// Provides a display conversion from Nullable <T>to value, where the conversion does not support implicit conversion and must be accompanied by a display conversion of () transformation
	public static explicit operator T(Nullable<T> value)
		return value.Value;

The two transformations are implicit and explicit because:

// The Nullable <T>set range here is: the value of all T + Null

// The implicit conversion from value to Nullable<T>is OK, because it is impossible to be Null on the right.
ndt = DateTime.Now;

// Implicit conversion from Nullable <T> to non-OK, because the right side may be Null, the converted value is meaningless at this point
DateTime dt = ndt; // Won't work!

// So you have to show the transformation, and if the hasvalue of ndt is false, an exception will be thrown
DateTime dt = (DateTime) ndt;

The previous GetValue function, if the hasValue of the value is false, throws an exception when reading the value. GetValueOrDefault avoids throwing an exception, but instead returns the type of value produced by the default constructor. It also has an overloaded function that specifies the default value:

DateTime dt = ndt.GetValueOrDefault(new DateTime(1900, 1, 1));

Code implemented internally may be as follows:

public struct Nullable<T> where T : struct // Pure supposition
	public T GetValueOrDefault()
		return HasValue ? Value : new T();	
 	public T GetValueOrDefault(T defaultValue)
		return HasValue ? Value : defaultValue;

Implement the remaining interfaces of Nullablt Type
It is the interface that remains to be implemented in Object: ToString, GetHashCode, and Equals functions, which are essentially the same as the value type, but are special treatments in the case of more Nullable s:

public struct Nullable<T> where T : struct // Pure supposition
	// Implement the ToString function
	public override string ToString()
		return HasValue ? Value.ToString() : "";
	public override int GetHashCode()
		return HasValue ? Value.GetHashCode() : 0;
	public override bool Equals(object obj)
		if (obj.GetType() != GetType())
			return false;
		Nullable<T> nt = (Nullable<T>)obj;
		if (nt.HasValue != HasValue)
			return false;
		return HasValue && Value.Equals(nt.Value);

Last step - make null available for Nullable types
Nullable is still a value type here and cannot be assigned with null, so it's C#'s compiler's turn to simplify this, if you write this line of code:

Nullable<DateTime> ndt = null;

The C#compiler will change it to the CIL corresponding to the following code:

Nullable<DateTime> ndt = new Nullable<DateTime>();// Default constructor, hasValue is false

C#Also put the following line of code:

ndt == null

Replace with:

// Ndt!=Null is the same

C#also simplifies the declarative syntax of Nullable Type:

Nullable<DateTime> ndt;
// Simplify to
DateTime? ndt;

bool? nb = null;// Equivalent to Nullable <bool> ndt;

bool? nb = true;// Equivalent to Nullable <bool> NDT = new Nullable <bool> (true);

// nb to bool still needs to be explicitly converted when used
if (nb != null)
	if ((bool)nb)// Must be explicitly converted
		... // true case
		... // false case

The last few writings:

// Assigning values to value types using Nullable Type,??Called null coalescing operator, dt is the latter when ndt is null
DateTime dt = ndt ?? new DateTime(2007, 1, 1);

Nullable class
In addition to the Nullable generic class, C#also provides a Nullable class, which is a static class that you can come to:

  • use to compare two objects based on nullable types
  • it also has a static method named GetUnderlyingType that you can use in connection with reflection.

CLR needs to identify nullable types for boxing
The previous operation was to design a generic class separately, then simplify the compiler without changing the CLR, but this would happen:

// Create an int that can be null
int? ni;
// Put it in the box and get obj
object obj = ni;// Since ni is a Nullable<int>type value type object, obj is not null at this time

In order to solve this problem, we need to change the CLR. The CLR needs to identify nullable types in order to boxing. If its hasValue is false when it is boxed, the CLR will force it to change to null.


Initialization order of constructors

// Success
ObjectField objField = new ObjectField
    objectType = typeof(GameObject),
    value = go,

// Runtime error
ObjectField objField = new ObjectField
    objectType = typeof(GameObject),
    value = go,

patial method

Partial classes are already clear, for example, if I have a class named MyClass that is partially executed under Editor, then I can write as follows:

// MyClass.cs file
partial class MyClass
	void MyFunc()

// MyClass.Editor.cs file
partial class MyClass
    void EditorFunc()

If this is the case, compiling under Runtime will cause errors, because EditorFunc cannot be executed in Runtime, it can be changed to this:

void MyFunc()

The disadvantage of this is to change the original Runtime script. Here's a partial method, which separates the declaration from the definition of a function by the following code:

// MyClass.Editor.cs file
partial class MyClass
	partial void EditorFunc();
    partial void EditorFunc()

this keyword appears in the static method parameter of C#

Reference link:https://stackoverflow.com/questions/846766/use-of-this-keyword-in-formal-parameters-for-static-methods-in-c-sharp
The code is as follows:

public static int Foo(this MyClass arg)

This writing, called Extension Method, feels like a C#legendary trick that adds a new method to a CLR type without changing the code or recompiling the original class.

For example, if you have a String and want to tell if it's in Email format, we might say this:

string email = GetARrandomString();

// A class is designed here that provides a static method for IsValid
if (EmailValidator.IsValid(email) ) {

With the Extension Method, you can write this directly:

string email = GetARrandomString();
if (email.IsValidEmailAddress()) {

The core code to implement the above functions is as follows:

public static class ScottGuExtensions
	// The core is the this keyword, which means to call this function through the string itself
    public static bool IsValidEmailAddress(this string s)
        Regex regex = new Regex(@"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$");
        return regex.IsMatch(s);

Then don't forget to use ScottGuExtensions or a prefix.

All in all, it's a Syntax Sugar. Nothing serious

Differences between Activator.CreateInstance and new

Reference material:https://stackoverflow.com/questions/1649066/activator-createinstancet-vs-new

For example, what's the difference between these two lines of code?

Student s1 = Activator.CreateInstance<Student>();
Student s1 = new Student();

The Activator.CreateInstance method is used to create generic objects, such as a static function:

// Type T must have a parameterless constructor
public static T Factory<T>() where T: new()
    return new T();

The type of new T() in this execution code is actually determined at compile time, and the compiler will convert it to a method that calls CallInstance.For example, if you have a type that is derived from var, it can create objects of that type through CreateInstance, but it can't be created using New, for example:

var viewType = Random.GetType();
var baseNodeView = Activator.CreateInstance(viewType);// Create objects of this type
var baseNodeView = new viewType();// error

Tags: C#

Posted on Sun, 05 Sep 2021 01:28:44 -0400 by mchannel