The Beauty of C# Advanced Notes

Links to the original text: https://www.jianshu.com/p/96b93f8a3a4a

C # (pronounced as C sharp, correctly written as C #), is a simple, modern, universal, object-oriented programming language, supporting cross-platform. Support structured, object-oriented, generic and other programming paradigms. It is deeply influenced by Visual Basic, Java and C/C++. Similar to Java in many ways, it has many advantages in object-oriented features.

Preface

This article does not include the more basic part of C# grammar, but only the more advanced grammar.

Empty type

C# provides a special data type, nullable type (nullable type), which can represent values within the normal range of its base value type, plus a null value.

The ability to assign null to numeric or Boolean types is particularly useful when dealing with databases and other data types that contain elements that may not be assigned. For example, a Boolean field in a database can store a value true or false, or it can be undefined.

The grammar of declaring an empty type is as follows:

data-type? variable-name = null;

The following example demonstrates the use of nullable data types:

using System;

namespace Empty type
{
    class NullablesAtShow
    {
        static void Main(string[] args)
        {
            int? num1 = null;
            int? num2 = 45;
            double? num3 = new double?();
            double? num4 = 3.14157;

            bool? boolval = new bool?();

            // Display value
            Console.WriteLine("Displays values of nullable types:" + num1 + ", " + num2 + ", " + num3 + ", " + num4);
            Console.WriteLine("An empty Boolean value:" + boolval);
        }
    }
}

The results are as follows:

Display nullable type values:, 45, 3.14157
 An empty Boolean value:

Null merge operator

Null merge operators are used to define default values for nullable and reference types. The Null merge operator defines a default value for type conversion in case the nullable type is Null. The Null merge operator implicitly converts the operand type to another nullable (or non-nullable) value type.

If the value of the first operand is null, the operator returns the value of the second operand, otherwise the value of the first operand is returned.

The following example demonstrates this:

using System;

namespace Empty type
{
    class NullablesAtShow
    {
        static void Main(string[] args)
        {
            double? num1 = null;
            double? num2 = 3.14157;
            double num3;
            num3 = num1 ?? 5.34;
            Console.WriteLine("num3 The value of:" + num3);
            num3 = num2 ?? 5.34;
            Console.WriteLine("num3 The value of:" + num3);
        }
    }
}

The results are as follows:

num3: 5.34
 num3: 3.14157

array

An example of initialization of an array:

double[] balance = new double[10];

There are many ways to assign an array:

  • Assign values to an array while declaring it, such as:

    double[] balance = { 2340.0, 4523.69, 3421.0 };
    
  • Create and initialize an array, such as:

    int[] marks = new int[5] { 99, 98, 92, 97, 95 };
    
  • In the previous case, the size of the array can be omitted, such as:

    int[] marks = new int[] { 99, 98, 92, 97, 95 };
    
  • Assign an array to another target array. In this case, the target and source point to the same memory location:

    int[] marks = new int[] { 99, 98, 92, 97, 95 };
    int[] score = marks;
    

Multidimensional Array

Three-dimensional array declaration example:

int[,,] m;

Multidimensional arrays can be initialized by specifying values for each line in parentheses. Here is an array with three rows and four columns:

int[,] a = new int[3, 4]  {
    { 0, 1, 2, 3} ,   // Initialize line 0
    { 4, 5, 6, 7} ,   // Initialize line 1
    { 8, 9, 10, 11}   // Initialize line 2
};

Similarly, in the above case, the size of the array can be omitted:

int[,] a = new int[,]  {
    { 0, 1, 2, 3} ,   // Initialize line 0
    { 4, 5, 6, 7} ,   // Initialize line 1
    { 8, 9, 10, 11}   // Initialize line 2
};

Staggered array

A staggered array is an array of arrays.

You can declare an interleaved array score with an int value, as follows:

int[][] scores;

Create the array:

scores = new int[5][]; 
for (int i = 0; i < scores.Length; i++)
{
    scores[i] = new int[4];
}

Since each row of the staggered array is an array, the number of rows must be specified when creating, that is, the number of one-dimensional arrays contained.

A staggered array can be initialized as follows:

scores = new int[2][]
{
    new int[] { 92, 93, 94 },
    new int[] { 85, 66, 87, 88 }
};

Similarly, the size of the array can be omitted:

scores = new int[][]
{
    new int[] { 92, 93, 94 },
    new int[] { 85, 66, 87, 88 }
};

Transfer array to function

When an array is used as a parameter of a function, the name of an array without an index can be used.

The following example demonstrates how to pass an array to a function:

using System;

namespace Transfer array to function
{
    class Program
    {
        static double getAverage(int[] arr, int size)
        {
            int i;
            double avg;
            int sum = 0;

            for (i = 0; i < size; ++i)
            {
                sum += arr[i];
            }

            avg = (double)sum / size;
            return avg;
        }

        static void Main(string[] args)
        {
            /* An int array with five elements */
            int[] balance = new int[] { 1000, 2, 3, 17, 50 };

            /* Pass the pointer of the array as a parameter */
            double avg = getAverage(balance, balance.Length);

            /* Output return value */
            Console.WriteLine("The average is:" + avg);
        }
    }
}

The results are as follows:

The average is 214.4.

Array of parameters

Sometimes, when a method is declared, the number of parameters to be passed to a function as a parameter cannot be determined. Arrays of C# parameters solve this problem. Arrays of parameters are usually used to pass unknown numbers of parameters to functions.

When using arrays as formal parameters, C# provides the params keyword, so that when calling methods with arrays as formal parameters, they can either pass array arguments or only a set of arrays. The format of params is:

public return type method name (params type name [] array name)

The following example demonstrates how to use parameter arrays:

using System;

namespace Array of parameters
{
    class Program
    {
        static int AddElements(params int[] arr)
        {
            int sum = 0;
            foreach (int i in arr)
            {
                sum += i;
            }
            return sum;
        }

        static void Main(string[] args)
        {
            int sum = AddElements(512, 720, 250, 567, 889);
            Console.WriteLine("The sum is:" + sum);
        }
    }
}

The results are as follows:

Total: 2938

Polymorphism

Static polymorphism

At compile time, the connection mechanism between functions and objects is called early binding, also known as static binding.

C # provides two techniques for static polymorphism:

  • function overloading
  • Operator overloading

Dynamic polymorphism

C# allows abstract classes to be created using the keyword abstract for the implementation of some classes that provide interfaces. When a derived class inherits from an abstract class, the implementation is complete.

Abstract classes contain Abstract methods, which can be implemented by derived classes. Derivatives have more professional functions.

Here are some rules about abstract classes:

  • Abstract classes cannot create instances and can only be inherited.
  • An abstract method cannot be declared outside an abstract class.

The following example demonstrates an abstract class:

using System;

namespace abstract class
{
    class Program
    {
        static void Main(string[] args)
        {
            Rectangle r = new Rectangle(10, 7);
            double a = r.area();
            Console.WriteLine("The measure of area:" + a);
        }
    }
}
using System;

namespace abstract class
{
    public class Rectangle : Shape
    {
        private int length;
        private int width;

        public Rectangle(int len = 0, int wid = 0)
        {
            length = len;
            width = wid;
        }

        public override int area()
        {
            Console.Write("Rectangle Class");
            return length * width;
        }
    }
}
namespace abstract class
{
    public abstract class Shape
    {
        public abstract int area();
    }
}

Separate code blocks represent different classes, each stored in a file.

The results are as follows:

Rectangle class area: 70

Virtual methods can be used when a function defined in a class needs to be implemented in an inherited class. Virtual methods are declared using the keyword virtual. Virtual methods can be implemented differently in different inheritance classes. Calls to virtual methods occur at runtime.

Dynamic polymorphism is achieved by abstract classes and virtual methods.

The following program demonstrates this:

using System;

namespace Virtual method
{
    class Program
    {
        static void Main(string[] args)
        {
            Caller c = new Caller();
            Rectangle r = new Rectangle(10, 7);
            Triangle t = new Triangle(10, 5);
            c.CallArea(r);
            c.CallArea(t);
        }
    }
}
using System;

namespace Virtual method
{
    public class Caller
    {
        public void CallArea(Shape sh)
        {
            int a = sh.area();
            Console.WriteLine("The measure of area:" + a);
        }
    }
}
using System;

namespace Virtual method
{
    public class Rectangle : Shape
    {
        public Rectangle(int wid = 0, int hei = 0) : base(wid, hei)
        {
        }

        public override int area()
        {
            Console.Write("Rectangle Class");
            return width * height;
        }
    }
}
using System;

namespace Virtual method
{
    public class Triangle : Shape
    {
        public Triangle(int wid = 0, int hei = 0) : base(wid, hei)
        {
        }

        public override int area()
        {
            Console.Write("Triangle Class");
            return width * height / 2;
        }
    }
}
using System;

namespace Virtual method
{
    public class Shape
    {
        protected int width, height;

        public Shape(int wid = 0, int hei = 0)
        {
            width = wid;
            height = hei;
        }

        public virtual int area()
        {
            Console.Write("Base class");
            return 0;
        }
    }
}

The results are as follows:

Rectangle class area: 70
 Area of Triangle class: 25

It can be noted that the override keyword is needed in the following two cases:

  • Derivative classes implement abstract methods of abstract base classes.
  • Derived classes implement virtual methods of base classes.

attribute

Abstract attributes

Abstract classes can have abstract properties that should be implemented in derived classes. The following example illustrates this point:

using System;

namespace Abstract attributes
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create a new Student object
            Student s = new Student();

            // Setting the code, name, and age of the student
            s.Code = "001";
            s.Name = "Zara";
            s.Age = 9;
            Console.WriteLine("Student Info: " + s);

            s.Age += 1; // Increasing Age
            Console.WriteLine("Student Info: " + s);
        }
    }
}
using System;

namespace Abstract attributes
{
    public class Student : Person
    {
        public string Code { get; set; } = "N.A";
        public override string Name { get; set; } = "N.A";
        public override int Age { get; set; } = 0;

        public override string ToString()
        {
            return "Code = " + Code + ", Name = " + Name + ", Age = " + Age;
        }
    }
}
using System;

namespace Abstract attributes
{
    public abstract class Person
    {
        public abstract string Name { get; set; }
        public abstract int Age { get; set; }
    }
}

The results are as follows:

Student Info: Code = 001, Name = Zara, Age = 9
Student Info: Code = 001, Name = Zara, Age = 10

Indexer

Indexer allows an object to be indexed like an array. When an indexer is defined for a class, it behaves like a virtual array. An instance of this class can be accessed using the array access operator ([]).

The syntax of one-dimensional indexer is as follows:

element-type this[int index] 
{
   // get accessor
   get 
   {
      // Returns the value specified by index
   }

   // set accessor
   set 
   {
      // Set the value specified by index 
   }
}

The declaration of the indexer's behavior is somewhat similar to that of an attribute. Like attributes, you can use get and set accessors to define indexers. However, an attribute returns or sets a specific data member, while an indexer returns or sets a specific value of an object instance. In other words, it divides the instance data into smaller parts, indexes each part, and gets or sets each part.

Defining an attribute includes providing the attribute name. The indexer is defined without a name, but with this keyword, which points to an object instance. The following example demonstrates this concept:

using System;

namespace Indexer
{
    class Program
    {
        static void Main(string[] args)
        {
            IndexedNames names = new IndexedNames();
            names[0] = "Zara";
            names[1] = "Riz";
            names[2] = "Nuha";
            names[3] = "Asif";
            names[4] = "Davinder";
            names[5] = "Sunil";
            names[6] = "Rubic";

            for (int i = 0; i < IndexedNames.size; i++)
            {
                Console.WriteLine(names[i]);
            }
        }
    }
}
using System;

namespace Indexer
{
    public class IndexedNames
    {
        private string[] namelist;
        public static int size = 10;

        public IndexedNames()
        {
            namelist = new string[size];

            for (int i = 0; i < size; i++)
            {
                namelist[i] = "N. A.";
            }
        }

        public string this[int index]
        {
            get
            {
                string tmp;

                // Array unbound
                if (index >= 0 && index <= size - 1)
                {
                    tmp = namelist[index];
                }
                else
                {
                    tmp = string.Empty;
                }
                
                return tmp;
            }

            set
            {
                if (index >= 0 && index <= size - 1)
                {
                    namelist[index] = value;
                }
            }
        }
    }
}

The results are as follows:

Zara
Riz
Nuha
Asif
Davinder
Sunil
Rubic
N. A.
N. A.
N. A.

Indexer Overloading

Indexers can be overloaded. Indexers can also be declared with multiple parameters, and each parameter can be of a different type. There is no need for indexers to be integer. Indexers can be of other types, such as string types.

using System;

namespace Indexer Overloading
{
    class Program
    {
        static void Main(string[] args)
        {
            IndexedNames names = new IndexedNames();
            names[0] = "Zara";
            names[1] = "Riz";
            names[2] = "Nuha";
            names[3] = "Asif";
            names[4] = "Davinder";
            names[5] = "Sunil";
            names[6] = "Rubic";

            // Use the first indexer with an int parameter
            for (int i = 0; i < IndexedNames.size; i++)
            {
                Console.WriteLine(names[i]);
            }

            // Use the second indexer with string parameters
            Console.WriteLine(names["Nuha"]);
        }
    }
}
using System;

namespace Indexer Overloading
{
   public class IndexedNames
   {
       private string[] namelist;
       public static int size = 10;

       public IndexedNames()
       {
           namelist = new string[size];

           for (int i = 0; i < size; i++)
           {
               namelist[i] = "N. A.";
           }
       }

       public string this[int index]
       {
           get
           {
               string tmp;

               // Array unbound
               if (index >= 0 && index <= size - 1)
               {
                   tmp = namelist[index];
               }
               else
               {
                   tmp = string.Empty;
               }

               return tmp;
           }

           set
           {
               if (index >= 0 && index <= size - 1)
               {
                   namelist[index] = value;
               }
           }
       }

       public int this[string name]
       {
           get
           {
               int index;

               for (index = 0; index < size; index++)
               {
                   if (namelist[index] == name)
                   {
                       return index;
                   }
               }

               return index;
           }
       }
   }
}

The results are as follows:

Zara
Riz
Nuha
Asif
Davinder
Sunil
Rubic
N. A.
N. A.
N. A.
2

Entrust

Delegates in C # are similar to pointers to functions in C/C++.

delegate is a reference type variable that holds a reference to a method. References can be changed at runtime.

Delegates are specifically used to implement events and callback methods. All delegates are derived from the System.Delegate class.

Statement of Entrustment

The delegation statement determines the method that can be referenced by the delegation. A delegate can point to a method with the same signature as it.

For example, suppose there is a delegation:

public delegate int MyDelegate(string s);

The delegate above can be used to refer to any method with a single string parameter and return an int type variable.

The grammar of the declaration is as follows:

delegate <return-type> <delegate-name>(<parameter-list>)

Instance delegation

Once the delegate type is declared, the delegate object must be created using the new keyword and related to a particular method. When a delegate is created, the parameters passed to the new statement are written like method calls, but without parameters. For example:

public delegate void printString(string s);
// ...
printString ps1 = new printString(WriteToScreen);
printString ps2 = new printString(WriteToFile);

The following example demonstrates the declaration, instantiation, and use of a delegate that can be used to reference a method with an integer parameter and return an integer value.

using System;

delegate int NumberChanger(int n);
namespace Entrust
{
    class Program
    {
        static void Main(string[] args)
        {
            TestDelegate d = new TestDelegate();

            // Create a delegate instance
            NumberChanger ncAdd = new NumberChanger(d.AddNum);
            NumberChanger ncMult = new NumberChanger(d.MultNum);

            // Calling methods using delegated objects
            ncAdd(25);
            Console.WriteLine("Value of Num: " + d.Num);
            ncMult(5);
            Console.WriteLine("Value of Num: " + d.Num);
        }
    }
}
using System;

namespace Entrust
{
   public class TestDelegate
   {
       public int Num { get; set; } = 10;

       public int AddNum(int p)
       {
           Num += p;
           return Num;
       }

       public int MultNum(int q)
       {
           Num *= q;
           return Num;
       }
   }
}

The results are as follows:

Value of Num: 35
Value of Num: 175

Delegated Multicast

Delegate objects can be merged using the + operator. A merge delegate calls the two delegates it merges. Only the same type of delegation can be merged. - Operators can be used to remove component delegates from merged delegates.

Using this feature, you can create a call list of the methods to be invoked when a delegate is invoked. This is called delegated multicasting, or multicast. The following example demonstrates delegated multicast:

using System;

delegate int NumberChanger(int n);
namespace Entrust
{
    class Program
    {
        static void Main(string[] args)
        {
            TestDelegate d = new TestDelegate();

            // Create a delegate instance
            NumberChanger nc;
            NumberChanger ncAdd = new NumberChanger(d.AddNum);
            NumberChanger ncMult = new NumberChanger(d.MultNum);

            nc = ncAdd;
            nc += ncMult;

            nc(5); // Call multicast
            Console.WriteLine("Value of Num: " + d.Num);
        }
    }
}
using System;

namespace Entrust
{
    public class TestDelegate
    {
        public int Num { get; set; } = 10;

        public int AddNum(int p)
        {
            Num += p;
            return Num;
        }

        public int MultNum(int q)
        {
            Num *= q;
            return Num;
        }
    }
}

The results are as follows:

Value of Num: 75

Purpose of commission

The following example demonstrates the use of delegates. Delegate printString can be used to refer to a method with a string as input without returning anything.

Use this delegate to invoke two methods, the first to print the string to the console, and the second to print the string to the file:

using System;

namespace Entrust
{
    class Program
    {
        static void Main(string[] args)
        {
            Print print = new Print();
            printString psScreen = new printString(print.WriteToScreen);
            printString psFile = new printString(print.WriteToFile);
            print.sendString(psScreen);
            print.sendString(psFile);
        }
    }
}
using System;
using System.IO;

// Statement of Entrustment
public delegate void printString(string s);
namespace Entrust
{
    public class Print
    {
        private FileStream fs;
        private StreamWriter sw;

        // Print to console
        public void WriteToScreen(string str)
        {
            Console.WriteLine("The String is: " + str);
        }
        
        // Print to file
        public void WriteToFile(string s)
        {
            fs = new FileStream(".\\message.txt", FileMode.Append, FileAccess.Write);
            sw = new StreamWriter(fs);
            sw.WriteLine(s);
            sw.Flush();
            sw.Close();
            fs.Close();
        }

        // Take delegation as a parameter and use it to invoke methods
        public void sendString(printString ps)
        {
            ps("Hello World");
        }
    }
}

The results are as follows:

$ dotnet run
The String is: Hello World

$ ls .
bin  message.txt  obj  Print.cs  Program.cs  TestDelegate.cs  Entrust.csproj
# You did create the message.txt file

$ cat message.txt
Hello World
# The message.txt file is also correct

Anonymous Methods

Anonymous method provides a technique for passing code blocks as delegation parameters. An anonymous method is a method that has no name but a principal.

There is no need to specify a return type in an anonymous method, which is inferred from the return statement within the method body.

The anonymous method is declared by creating a delegate instance. For example:

delegate void NumberChanger(int n);
// ...
NumberChanger nc = delegate(int x)
{
    Console.WriteLine("Anonymous Method: " + x);
};

The code block Console. WriteLine ("Anonymous Method:" +x); is the body of an anonymous method.

Delegates can be called either anonymously or by named methods, i.e., by passing method parameters to the delegate object. For example:

nc(10);

The following example demonstrates the concept of anonymous methods:

using System;

namespace Anonymous Methods
{
    class Program
    {
        static void Main(string[] args)
        {
            TestDelegate d = new TestDelegate();

            // Creating delegate instances using anonymous methods
            NumberChanger ncAno = delegate (int x)
            {
                Console.WriteLine("Anonymous Method: " + x);
            };

            // Calling delegates using anonymous methods
            ncAno(10);

            // Instantiating delegates using naming methods
            NumberChanger ncNamed1 = new NumberChanger(d.AddNum);

            // Calling delegates using named methods
            ncNamed1(5);

            // Instantiating delegates using another naming method
            NumberChanger ncNamed2 = new NumberChanger(d.MultNum);

            // Calling delegates using named methods
            ncNamed2(2);
        }
    }
}
using System;

public delegate void NumberChanger(int n);
namespace Anonymous Methods
{
    public class TestDelegate
    {
        public int Num { get; set; } = 10;

        public void AddNum(int p)
        {
            Num += p;
            Console.WriteLine("Named Method: " + Num);
        }

        public void MultNum(int q)
        {
            Num *= q;
            Console.WriteLine("Named Method: " + Num);
        }
    }
}

The results are as follows:

Anonymous Method: 10
Named Method: 15
Named Method: 30

Event

Events are basically user actions, such as keystrokes, clicks, mouse movements, etc., or some occurrences, such as notifications generated by the system. Applications need to respond to event s, such as interruptions, when they occur. Events are used for interprocess communication.

Use delegates through events

Events are declared and generated in classes, and are associated with event handlers by using delegations in the same class or other classes.

  • Classes containing events are used to publish events, called publisher classes.
  • Other classes that accept this event are called subscriber classes.

Events use the publisher-subscriber model.

  • Publisher is an object that contains event and delegate definitions. The relationship between events and delegates is also defined in this object. Objects of the publisher class call this event and notify other objects.
  • Subscriber is an object that accepts events and provides event handlers. Delegates in the publisher class call methods in the subscriber class (event handlers).

Declaring events

To declare an event within a class, you must first declare the delegate type of the event. For example:

public delegate void BoilerLogHandler(string status);

Then declare the event itself, using the event keyword:

// Delegate Definition Events Based on the above
public event BoilerLogHandler BoilerEventLog;

The above code defines a delegate named BoilerLogHandler and an event named BoilerEventLog, which invokes the delegate when it is generated.

Example 1

using System;

namespace Event
{
    class Program
    {
        static void Main(string[] args)
        {
            EventTest e = new EventTest(5);
            e.SetNum(7);
            e.SetNum(11);
        }
    }
}
using System;

namespace Event
{
    public class EventTest
    {
        private int Num;
        private delegate void NumManipulationHandler();
        private event NumManipulationHandler ChangeNum;

        private void OnNumChanged()
        {
            if (ChangeNum != null)
            {
                ChangeNum();
            }
            else
            {
                Console.WriteLine("Event fired!");
            }
        }

        public EventTest(int n)
        {
            SetNum(n);
        }

        public void SetNum(int n)
        {
            if (Num != n)
            {
                Num = n;
                OnNumChanged();
            }
        }
    }
}

The results are as follows:

Event fired!
Event fired!
Event fired!

Example 2

using System;

namespace Event
{
    // event subscriber
    class RecordBoilerInfo
    {
        static void Logger(string info)
        {
            Console.WriteLine(info);
        }

        static void Main(string[] args)
        {
            BoilerInfoLogger filelog = new BoilerInfoLogger(".\\boiler.txt");
            DelegateBoilerEvent boilerEvent = new DelegateBoilerEvent();
            boilerEvent.BoilerEventLog += new DelegateBoilerEvent.BoilerLogHandler(Logger);
            boilerEvent.BoilerEventLog += new DelegateBoilerEvent.BoilerLogHandler(filelog.Logger);
            boilerEvent.LogProcess();
            filelog.Close();
        }
    }
}
using System;
using System.IO;

namespace Event
{
    // Retain the terms written to the log file
    public class BoilerInfoLogger
    {
        private FileStream fs;
        private StreamWriter sw;

        public BoilerInfoLogger(string filename)
        {
            fs = new FileStream(filename, FileMode.Append, FileAccess.Write);
            sw = new StreamWriter(fs);
        }

        public void Logger(string info)
        {
            sw.WriteLine(info);
        }

        public void Close()
        {
            sw.Close();
            fs.Close();
        }
    }
}
using System;

namespace Event
{
    // Event Publisher
    public class DelegateBoilerEvent
    {
        public delegate void BoilerLogHandler(string status);

        // Delegate Definition Events Based on the above
        public event BoilerLogHandler BoilerEventLog;

        public void LogProcess()
        {
            string remarks = "O. K";
            Boiler b = new Boiler(100, 12);
            int t = b.Temp;
            int p = b.Pressure;
            if (t > 150 || t < 80 || p < 12 || p > 15)
            {
                remarks = "Need Maintenance";
            }
            OnBoilerEventLog("Logging Info:" + Environment.NewLine);
            OnBoilerEventLog("Temparature: " + t + Environment.NewLine + "Pressure: " + p + Environment.NewLine);
            OnBoilerEventLog("Message: " + remarks);
        }

        private void OnBoilerEventLog(string message)
        {
            if (BoilerEventLog != null)
            {
                BoilerEventLog(message);
            }
        }
    }
}
using System;

namespace Event
{
    public class Boiler
    {
        public int Temp { get; set; }
        public int Pressure { get; set; }

        public Boiler(int temp, int pressure)
        {
            Temp = temp;
            Pressure = pressure;
        }
    }
}

The results are as follows:

Logging Info:

Temparature: 100
Pressure: 12

Message: O. K

Characteristic

attribute is a declarative tag used to transfer behavior information of various elements in a program (such as classes, methods, structures, enumerations, components, etc.) at runtime. You can add declarative information to a program by using features. A declarative label is described by placing square brackets ([]) in front of the elements it applies.

Features are used to add metadata, such as compiler instructions and annotations, descriptions, methods, classes, and other information.

Prescribed characteristics

The grammar of the specified features is as follows:

[attribute(positional_parameters, name_parameter = value, ...)]
element

The name and value of a property are specified in square brackets and placed before the elements it applies. positional_parameters specify the necessary information, and name_parameter s specify the optional information.

Predefined features

NET provides the following features:

  • AttributeUsage
  • Conditional
  • Obsolete

AttributeUsage

Predefined AttributeUsage describes how to use a custom feature class. It specifies the types of projects that features can be applied to.

The grammar that specifies this feature is as follows:

[AttributeUsage(
    validon,
    AllowMultiple = allowmultiple,
    Inherited = inherited
)]

Among them,

  • validon specifies language elements where features can be placed. It is a combination of the values of the enumerator AttributeTargets. The default value is AttributeTargets.All.
  • AllowMultiple (optional) provides a Boolean value for the AllowMultiple attribute of this feature. If true, this feature is versatile. The default value is false, which is used alone.
  • The parameter inherited (optional) provides a Boolean value for the Inherited attribute of this feature. If true, this feature can be inherited by derived classes. The default value is false and cannot be inherited.

For example:

[AttributeUsage(
    AttributeTargets.Class |
    AttributeTargets.Constructor |
    AttributeTargets.Field |
    AttributeTargets.Method |
    AttributeTargets.Property,
    AllowMultiple = true
)]

Conditional

This predefined feature marks a conditional method whose execution depends on its top preprocessing identifier.

It causes conditional compilation of method calls, depending on the specified values, such as Debug or Trace. For example, display the value of a variable when debugging code.

The grammar that specifies this feature is as follows:

[Conditional(
    conditionalSymbol
)]

For example:

[Conditional("DEBUG")]

The following example demonstrates this feature:

using System;

namespace Characteristic
{
    class Program
    {
        static void func1()
        {
            MyClass.Message("In function 1.");
            func2();
        }

        static void func2()
        {
            MyClass.Message("In function 2.");
        }
        
        static void Main(string[] args)
        {
            MyClass.Message("In Main function.");
            func1();
        }
    }
}
#define DEBUG
using System;
using System.Diagnostics;

namespace Characteristic
{
    public class MyClass
    {
        [Conditional("DEBUG")]
        public static void Message(string msg)
        {
            Console.WriteLine(msg);
        }
    }
}

The results are as follows:

In Main function.
In function 1.
In function 2.

Obsolete

This predefined feature marks program entities that should not be used. It can tell the compiler to discard a particular target element. For example, when a new method is used in a class but still wants to preserve the old method in the class, it can be marked obsolete by displaying a message that should use the new method instead of the old one.

The grammar that specifies this feature is as follows:

[Obsolete(
    message
)]
[Obsolete(
    message,
    iserror
)]

Among them,

  • message is a string describing the cause of the project's obsolescence and what to use instead.
  • iserror is a Boolean value. If the value is true, the compiler should treat the use of the project as an error. The default value is false, and the compiler generates a warning.

The following example demonstrates this feature:

using System;

namespace Characteristic
{
    class Program
    {
        static void Main(string[] args)
        {
            MyClass.OldMethod();
        }
    }
}
#define DEBUG
using System;

namespace Characteristic
{
    public class MyClass
    {
        [Obsolete("Don't use OldMethod, use NewMethod instead", true)]
        public static void OldMethod()
        {
            Console.WriteLine("This is the old method.");
        }

        public static void NewMethod()
        {
            Console.WriteLine("This is the new method.");
        }
    }
}

When attempting to compile the program, the compiler will give an error message stating:

Don't use OldMethod, use NewMethod instead

Create custom features

Custom features can be created to store declarative information and retrieve it at runtime. This information can be related to any target element based on design criteria and application requirements.

Creating and using custom features consists of four steps:

  • Declare custom features;
  • Build custom features;
  • Apply custom features on target program elements;
  • Access features by reflection.

The last step involves writing a simple program to read metadata to find various symbols. Metadata is data and information used to describe other data. The program should use reflection to access features at runtime.

Declare custom features

A new custom feature should be derived from the System.Attribute class. For example:

// A custom feature BugFix is assigned to classes and their members
[AttributeUsage(
    AttributeTargets.Class |
    AttributeTargets.Constructor |
    AttributeTargets.Field |
    AttributeTargets.Method |
    AttributeTargets.Property,
    AllowMultiple = true
)]
public class DebugInfo : System.Attribute

A custom feature called DebugInfo is declared.

Building custom features

Next, build a custom feature called DebugInfo, which stores the information obtained by the debugger. It stores the following information:

  • Code number of bug
  • Identify the developer name of the bug
  • Date of last review of the code
  • A string message that stores developer Tags

The DebugInfo class will have three private attributes for storing the first three information and one public attribute for storing messages. So bug numbers, developer names, and review dates will be required positional parameters for the DebugInfo class, and messages will be an optional named parameter.

Each feature must have at least one constructor. Necessary positioning parameters should be passed through constructors. The following code demonstrates the DebugInfo class:

using System;

namespace Characteristic
{
    // A custom feature BugFix is assigned to classes and their members
    [AttributeUsage(
        AttributeTargets.Class |
        AttributeTargets.Constructor |
        AttributeTargets.Field |
        AttributeTargets.Method |
        AttributeTargets.Property,
        AllowMultiple = true
    )]
    public class DebugInfo : System.Attribute
    {
        public int BugNumber { get; }
        public string Developer { get; }
        public string LastReview { get; }
        public string Message { get; set; }

        public DebugInfo(int bugNumber, string developer, string lastView)
        {
            BugNumber = bugNumber;
            Developer = developer;
            LastReview = lastView;
        }
    }
}

Applying custom features

Apply the feature by placing it immediately before its target:

using System;

namespace Characteristic
{
    [DebugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")]
    [DebugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")]
    public class Rectangle
    {
        // Membership variables
        protected double length;
        protected double width;
        
        public Rectangle(double l, double w)
        {
            length = l;
            width = w;
        }

        [DebugInfo(55, "Zara Ali", "19/10/2012", Message = "Return type mismatch")]
        public double GetArea()
        {
            return length * width;
        }

        [DebugInfo(56, "Zara Ali", "19/10/2012")]
        public void Display()
        {
            Console.WriteLine("Length: " + length);
            Console.WriteLine("Width: " + width);
            Console.WriteLine("Area: " + GetArea());
        }
    }
}

reflex

reflection objects are used to obtain type information at run time. Located in the System.Reflection namespace, this class has access to the metadata of a running program, which contains classes that allow access to application information and dynamically add types, values, and objects to the application.

Reflection has the following uses:

  • Allows information about properties to be viewed at runtime.
  • Allows you to review the various types in the collection and instantiate them.
  • Methods and attributes that allow delayed binding.
  • Allows new types to be created at runtime and then used to perform some tasks.

As mentioned earlier, reflection allows you to view information about attributes.

The MemberInfo object of the System.Reflection class needs to be initialized to discover class-related properties. To do this, an object of the target class can be defined as follows:

System.Reflection.MemberInfo info = typeof(MyClass);

The following program demonstrates this:

using System;

namespace reflex
{
    class Program
    {
        static void Main(string[] args)
        {
            System.Reflection.MemberInfo info = typeof(MyClass);
            var attributes = info.GetCustomAttributes(true);
            for (int i = 0; i < attributes.Length; i++)
            {
                Console.WriteLine(attributes[i]);
            }
        }
    }
}
namespace reflex
{
    [HelpAttribute("Information on the class MyClass")]
    public class MyClass
    {
    }
}
using System;

namespace reflex
{
    [AttributeUsage(AttributeTargets.All)]
    public class HelpAttribute : System.Attribute
    {
        public readonly string Url;
        public string Topic { get; set; } // named parameter

        public HelpAttribute(string url)
        {
            Url = url;
        }
    }
}

The result shows the custom attributes attached to the class MyClass:

Reflection. HelpAttribute

The following example uses the DebugInfo attribute created in the feature section and uses reflection to read metadata in the Rectangle class.

using System;
using System.Reflection;

namespace reflex
{
    class Program
    {
        static void Main(string[] args)
        {
            Rectangle r = new Rectangle(4.5, 7.5);
            r.Display();
            var type = typeof(Rectangle);

            // Traversing the properties of the Rectangle class
            foreach (Object attributes in type.GetCustomAttributes(false))
            {
                DebugInfo dbi = (DebugInfo)attributes;
                if (dbi != null)
                {
                    Console.WriteLine("Bug Number: " + dbi.BugNumber);
                    Console.WriteLine("Developer: " + dbi.Developer);
                    Console.WriteLine("Last Reviewed: " + dbi.LastReview);
                    Console.WriteLine("Remarks: " + dbi.Message);
                }
            }

            // Traversal method attributes
            foreach (MethodInfo m in type.GetMethods())
            {
                foreach (Attribute a in m.GetCustomAttributes(true))
                {
                    DebugInfo dbi = (DebugInfo)a;
                    if (dbi != null)
                    {
                        Console.WriteLine("Bug Number: " + dbi.BugNumber + ", " + "for Method: " + m.Name);
                        Console.WriteLine("Developer: " + dbi.Developer);
                        Console.WriteLine("Last Reviewed: " + dbi.LastReview);
                        Console.WriteLine("Remarks: " + dbi.Message);
                    }
                }
            }
        }
    }
}
using System;

namespace reflex
{
    [DebugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")]
    [DebugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")]
    public class Rectangle
    {
        // Membership variables
        protected double length;
        protected double width;

        public Rectangle(double l, double w)
        {
            length = l;
            width = w;
        }

        [DebugInfo(55, "Zara Ali", "19/10/2012", Message = "Return type mismatch")]
        public double GetArea()
        {
            return length * width;
        }

        [DebugInfo(56, "Zara Ali", "19/10/2012")]
        public void Display()
        {
            Console.WriteLine("Length: " + length);
            Console.WriteLine("Width: " + width);
            Console.WriteLine("Area: " + GetArea());
        }
    }
}
using System;

namespace reflex
{
    // A custom feature BugFix is assigned to classes and their members
    [AttributeUsage(
        AttributeTargets.Class |
        AttributeTargets.Constructor |
        AttributeTargets.Field |
        AttributeTargets.Method |
        AttributeTargets.Property,
        AllowMultiple = true
    )]
    public class DebugInfo : System.Attribute
    {
        public int BugNumber { get; }
        public string Developer { get; }
        public string LastReview { get; }
        public string Message { get; set; }

        public DebugInfo(int bugNumber, string developer, string lastView)
        {
            BugNumber = bugNumber;
            Developer = developer;
            LastReview = lastView;
        }
    }
}

The results are as follows:

Length: 4.5
Width: 7.5
Area: 33.75
Bug Number: 45
Developer: Zara Ali
Last Reviewed: 12/8/2012
Remarks: Return type mismatch
Bug Number: 49
Developer: Nuha Ali
Last Reviewed: 10/10/2012
Remarks: Unused variable
Bug Number: 55, for Method: GetArea
Developer: Zara Ali
Last Reviewed: 19/10/2012
Remarks: Return type mismatch
Bug Number: 56, for Method: Display
Developer: Zara Ali
Last Reviewed: 19/10/2012
Remarks:

Pointer

Unsafe code

C# allows pointer variables to be used in functions when a block of code is marked with unsafe modifiers. Insecure code or unmanaged code is a block of code that uses pointer variables.

The following example illustrates the use of pointers when using unsafe modifiers in C#:

using System;

namespace Pointer
{
    class Program
    {
        static unsafe void Main(string[] args)
        {
            int var = 20;
            int* p = &var;
            Console.WriteLine("Data is: " + var);
            Console.WriteLine("Address is: " + (int)p);
        }
    }
}

The results are as follows:

Data is: 20
Address is: -2068326148

Retrieving data values using pointers

You can use the ToString() method to retrieve data stored in the location referenced by the pointer variable.

In addition, you don't need to declare the whole method as unsafe code, just declare a part of the method as unsafe code.

The following example demonstrates this:

using System;

namespace Pointer
{
    class Program
    {
        static void Main()
        {
            unsafe
            {
                int var = 20;
                int* p = &var;
                Console.WriteLine("Data is: " + var);
                Console.WriteLine("Data is: " + p->ToString());
                Console.WriteLine("Address is: " + (int)p);
            }
        }
    }
}

The results are as follows:

Data is: 20
Data is: 20
Address is: 1301797540

Of course, you can also use * p directly, the effect is the same.

Accessing array elements with pointers

In C #, the array name and a pointer to the same data type as the array data are different variable types. For example, int *p and int[] p are different types. The pointer variable p can be increased because it is not fixed in memory, but the array address is fixed in memory, so the array P cannot be increased.

Therefore, if you need to use pointer variables to access array data (as you did in C/C++), you need to use fixed() statements to fix pointers.

The following example demonstrates this:

using System;

namespace Pointer
{
    class Program
    {
        static void Main()
        {
            unsafe
            {
                int[] list = { 10, 100, 200 };
                fixed (int* ptr = list)
                {
                    /* Display the array address in the pointer */
                    for (int i = 0; i < 3; i++)
                    {
                        Console.WriteLine("Address of list[" + i + "] = " + (int)(ptr + i));
                        Console.WriteLine("Value of list[" + i + "] = " + *(ptr + i));
                    }
                }
            }
        }
    }
}

The results are as follows:

Address of list[0] = -1312682000
Value of list[0] = 10
Address of list[1] = -1312681996
Value of list[1] = 100
Address of list[2] = -1312681992
Value of list[2] = 200

Compiling unsafe code

To compile unsafe code, you need to use the - unsafe compiler option.

For example, for Visual Studio IDE, you need to enable security code in project properties:

  1. Open the Properties page of the project.
  2. Click the Build property page.
  3. Select the Allow Insecure Codes check box.

The principle is that in the project's. csproj file, the following elements are added:

<PropertyGroup>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

Therefore, if you use other development environments, you can try to modify the project's. csproj file directly in the way described above.

Multithreading

Threads are defined as execution paths of programs. Each thread defines a unique control flow. If an application involves complex and time-consuming operations, it is often useful to set different thread execution paths, each thread performing specific tasks.

Threads are lightweight processes. A common example of using threads is the implementation of parallel programming in modern operating systems. Using threads saves CPU cycles and improves application efficiency.

Thread life cycle

The thread life cycle begins when the thread object is created and ends when the thread is terminated or executed.

The following lists the various states in the thread life cycle:

  • No startup status. The condition when a thread instance is created but the Start() method is not called.
  • Ready state. What happens when the thread is ready to run and wait for the CPU cycle.
  • Non-operational status. Threads are not runnable in the following situations:
    • Sleep() method has been called
    • Wait() method has been called
    • Blocking through I/O operations
  • The state of death. The condition of a thread when it has completed execution or aborted.

Main thread

The System.Threading.Thread class is used for the work of threads. It allows you to create and access a single thread in a multithreaded application. The first thread executed in a process is called the main thread.

When the program starts to execute, the main thread is created automatically. Threads created using the Thread class are invoked by sub-threads of the main thread. Threads can be accessed using the CurrentThread property of the Thread class.

The following example demonstrates the execution of the main thread:

using System;
using System.Threading;

namespace Multithreading
{
    class MainThread
    {
        static void Main(string[] args)
        {
            Thread th = Thread.CurrentThread;
            th.Name = "MainThread";
            Console.WriteLine("This is " + th.Name);
        }
    }
}

The results are as follows:

This is MainThread

Create threads

Threads are created by extending the Thread class. The extended Thread class calls the Start() method to start the execution of the sub-thread.

The following example demonstrates this concept:

using System;
using System.Threading;

namespace Multithreading
{
    class ThreadCreation
    {
        static void CallToChildThread()
        {
            Console.WriteLine("Child thread starts");
        }

        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine("In Main: Creating the Child thread");
            Thread childThread = new Thread(childref);
            childThread.Start();
        }
    }
}

The results are as follows:

In Main: Creating the Child thread
Child thread starts

Management threads

The Thread class provides various ways to manage threads.

The following example demonstrates the use of the sleep() method to pause threads at a specific time.

using System;
using System.Threading;

namespace Multithreading
{
    public class ThreadSleep
    {
        static void CallToChildThread()
        {
            Console.WriteLine("Child thread starts");

            // Thread pause 5000ms
            int sleepfor = 5000;
            Console.WriteLine("Child Thread Paused for " + sleepfor / 1000 + " seconds");
            Thread.Sleep(sleepfor);
            Console.WriteLine("Child thread resumes");
        }

        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine("In Main: Creating the Child thread");
            Thread childThread = new Thread(childref);
            childThread.Start();
        }
    }
}

The results are as follows:

In Main: Creating the Child thread
Child thread starts
Child Thread Paused for 5 seconds
# Wait 5 seconds
Child thread resumes

Destroy threads

The Abort() method is used to destroy threads.

Discontinue threads at runtime by throwing ThreadAbortException. If there is a final block, the control is sent to the final block.

The following example illustrates this point:

using System;
using System.Threading;

namespace Multithreading
{
    public class ThreadDestruction
    {
        static void CallToChildThread()
        {
            try
            {
                Console.WriteLine("Child thread starts");

                // Count to 10
                for (int counter = 0; counter <= 10; counter++)
                {
                    Thread.Sleep(500);
                    Console.WriteLine(counter);
                }
                Console.WriteLine("Child Thread Completed");
            }
            catch (ThreadAbortException)
            {
                Console.WriteLine("Thread Abort Exception");
            }
            finally
            {
                Console.WriteLine("Couldn't catch the Thread Exception");
            }
        }

        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine("In Main: Creating the Child thread");
            Thread childThread = new Thread(childref);
            childThread.Start();
            Thread.Sleep(2000); // Stop the main thread for a period of time

            // Now abort the subthread
            Console.WriteLine("In Main: Aborting the Child thread");
            childThread.Abort();
        }
    }
}

The results are as follows:

In Main: Creating the Child thread
Child thread starts
0
1
2
In Main: Aborting the Child thread
Thread Abort Exception
Couldn't catch the Thread Exception

 

 

 

 

Tags: Attribute Programming Java Database

Posted on Fri, 06 Sep 2019 01:15:54 -0700 by napurist