C # schematic tutorial 24th reflections and Features

Source: Internet
Author: User

Reflections and properties
Meta Data and reflection
Type class
Get type Object
What is an attribute
Application features
Pre-defined reserved attributes
Obsolete (OBSOLETE) characteristics
Conditional characteristics
Caller information attribute
DebuggerStepThrough characteristics
Other pre-defined features
More about app Features
Multiple features
Other types of targets
Global attributes
Custom Attributes
Declaring Custom Attributes
constructors that use attributes
Specifying constructors
Using constructors
Positional parameters and named parameters in constructors
Restrictions on the use of features
Best Practices for custom attributes
Access attributes
Using the IsDefined method
Using the GetCustomAttributes method

Note

  • The metadata for the class contains the members and attributes of the class
  • The metadata of the program can be understood as the structure information of the program
  • Reflection (reflection) is used to view meta data
  • Reflected by the type class in C #
  • Attribute (attribute) to add metadata to a type

PS: Understanding needs to be strengthened


Reflection and attribute metadata and reflection

Most programs process data, including reading, writing, manipulating, and displaying data. (Graphics are also a form of data.) However, for some programs, the data they manipulate is not numbers, text, or graphics, but information about the program and program type itself.

    • Data about programs and their types are called metadata (metadata), which are stored in the program's assembly
    • At run time, the program can view the metadata of other assemblies or their own. a running program looks at its own metadata or other program's meta-data behavior called reflection (reflection)

Object Browser is an example of an explicit metadata program. It can read the assembly and then display all the attributes and members of the contained type, as well as the type.
This chapter describes how your program uses the type class to reflect data, and how programmers can use attributes to add metadata to a type.

To use reflection, we must use the System.Reflection namespace.

Type class

You have previously described how to declare and use types in C #. Includes predefined types (int, long, and string, etc.), types in the BCL (Console, IEnumerable, and so on), and user-defined types (MyClass, Mydel, and so on). Each of these types has its own members and attributes.
The BCL declares an abstract class called type, which is designed to contain the attributes of a type. Using objects of this class allows us to obtain information about the type used by the program.
Because type is an abstract class, it cannot have an instance. Instead, at run time, the CLR creates an instance of a class derived from type (RuntimeType) that contains type information. When we want to access these instances, the CLR does not return a reference to a derived class, but a reference to the type base class. However, for the sake of simplicity, in the remainder of this chapter, I will refer to the object that the reference refers to as a type object (although technically a derived type object within the BCL).
Important things to know about type are as follows:

    • For each type used in the program, the CLR creates an object of type types containing this type of information
    • Each type used in a program is associated with an object of a separate type class
    • No matter how many instances of a type are created, only one type object is associated to all of these instances

Shows a running program that has two MyClass objects and one Otherclass object. Note that, although there are two instances of MyClass, there is only one type object to represent it.

We can get almost all the information about the type we need to know from the type object. The following table lists the more useful members in the class.

Get type Object

This section learns to use the GetType method and the typeof operator to get the type object. The object type contains a method called GetType that returns a reference to the type object of the instance. Since each type is ultimately inherited from object, we can use the GetType method on any type of object to get its type object, as follows:
Type t = myInstance.GetType();
The following code shows how to declare a base class and the subclasses derived from it. The main method creates an instance of each class and places the references in an array called BCA for ease of use. In the outer-layer foreach loop, the code obtains the type object and outputs the class's name, and then gets the field of the class and outputs it. Demonstrates an in-memory object.

using System;using System.Reflection;class BaseClass{    public int BaseField=0;}class DerivedClass:BaseClass{    public int DerivedField=0;}class Program{    static void Main()    {        var bc=new BaseClass();        var dc=new DerivedClass();        BaseClass[] bca=new BaseClass[]{bc,dc};        foreach(var v in bca)        {            Type t=v.GetType();            Console.WriteLine("Object type : {0}",t.Name);            FieldInfo[] fi=t.GetFields();            foreach(var f in fi)            {                Console.WriteLine("    Field : {0}",f.Name);            }            Console.WriteLine();        }    }}


We can also use the TypeOf operator to get the type object. Just provide the type name as the operand, and it returns a reference to the type object, as follows:

Type t = typeof(DerivedClass);            ↑        ↑         运算符 希望的Type对象的类型

The following code gives a simple example of using the TypeOf operator:

using System;using System.Reflection;namespace SimpleReflection{    class BaseClass    {        public int MyFieldBase;    }    class DerivedClass:BaseClass    {        public int MyFieldDerived;    }    class Program    {        static void Main()        {            Type tbc=typeof(DerivedClass);            Console.WriteLine("Result is {0}.",tbc.Name);            Console.WriteLine("It has the following fields:");            FieldInfo[] fi=tbc.GetFields();            foreach(var f in fi)            {                Console.WriteLine("    {0}",f.Name);            }        }    }}

What is an attribute

An attribute (attribute) is a language structure that allows us to add metadata to the program's assembly. It is a special type of class for saving program structure information.

    • Program construct, which applies an attribute, is called the target
    • A program designed to obtain and use metadata (such as an Object Browser) is called a consumer of attributes (consumer)
    • . NET has a lot of features, we can also declare custom attributes

is an overview of the relevant components in the use of attributes, and also shows the following points about features.

    • We apply attributes to the program structure in the source code
    • The compiler gets the source code and generates metadata from the attribute, and then puts the metadata in the assembly
    • The consumer program can obtain metadata about the attributes and other components in the program. Note that the compiler concurrently produces and consumes features

By convention, the attribute name uses the Pascal nomenclature and ends with the attribute suffix. When applying attributes to a target, we can not use suffixes. For example, for both SerializableAttribute and Myattributeattribute, we can use serializable and myattribute short names when applying them to structs.

Application features

Instead of explaining how to create an attribute, let's look at how to use a defined feature. In this way, you will have a general understanding of how they are used.
The purpose of the attribute is to tell the compiler to embed a set of metadata for the program structure into the assembly. We can do this by applying attributes to the structure.

    • Placing feature fragments before a structure to apply properties
    • Attribute fragments are surrounded by square brackets, which are parameter lists for attribute names and attributes

For example, the following code shows the beginning of a two class. The first few lines of code demonstrate the application of a feature called Serializable to the MyClass. Note that serializable does not have a parameter list. The declaration of the second class has an attribute called MyAttribute, which has a parameter list with two string arguments.

[Serializable]public class MyClass{    …}[MyAttribute("Simple class","Version 3.57")]public class MyOtherClass{    …}

Important things to know about the features are as follows:

    • Most features are only for structures that follow directly behind one or more feature fragments
    • The structure that applied the feature is called the decoration of the piece (decorated or adorned, both are used very commonly)
Pre-defined reserved attributes

Before you learn how to define your own features, this section introduces a few. NET pre-defined attributes.

Obsolete (OBSOLETE) characteristics

A program may experience multiple releases during its life cycle, and it is likely to last for many years. In the latter part of the program life cycle, programmers often need to write new methods of similar functionality to replace old methods. For a variety of reasons, you may not want to use old code that calls obsolete old methods, but you just want to call new methods with the newly written code.
If this is the case, you certainly want the team member or programmer who will be working on the code later to use only the new code. To warn them not to use the old method, you can use the obsolete attribute to label the program structure as out-of-date, and a warning message that is explicitly useful when the code is compiled. The following code gives an example of the use:

class Program{    //应用特性    [Obsolete("User method SuperPrintOut")]    static void PrintOut(string str)    {        Console.WriteLine(str);    }    static void Main(string[] args)    {        PrintOut("Start of Main");    }}

Note that even if printout is marked as expired, the main method calls it. Code compilation also works well and produces the following output:

However, during compilation, the compiler produces the following CS0618 warning message informing us that an outdated structure is being used:

Another overload of the obsolete attribute accepts the second argument of type bool. This parameter specifies whether the target should be flagged as an error and not just a glance. The following code specifies that it needs to be marked as an error:

                                   标记为错误                                       ↓[Obsolete("User method SuperPrintOut",true)]static void PrintOut(string str){    …}
Conditional characteristics

Note
Conditional features are similar to conditional compilation of C language

The conditional feature allows us to include or exclude all calls to a particular method. Apply the conditional attribute to the method declaration and use the compiler as a parameter.

    • If a compilation symbol is defined, then the compiler will include all code that calls this method, which is no different from the normal method
    • If no compilation symbols are defined, then the compiler ignores all calls to this method in the code

The CIL code that defines the method itself is always included in the assembly. Just the calling code will be inserted or ignored.
For example, in the following code, apply the conditional attribute to the declaration of a method called Tracemessage. Attribute has only one parameter, here is the string dotrace.

    • When the compiler compiles this code, it checks to see if a compilation symbol is defined as Dotrace
    • If Dotrace is defined, the compiler will include all calls to the Tracemessage method as usual
    • If no dotrace such a compilation symbol is defined, the compiler will not output any calls to Tracemessage
[Conditional("DoTrace")]static void TraceMessage(string str){    Console.WriteLine(str);}

examples of conditional features
The following code shows a complete example of using the conditional attribute.

    • The Main method contains two calls to the Tracemessage method
    • The declaration of the Tracemessage method is decorated with the conditional attribute with the Dotrace compilation symbol as the parameter. Therefore, if dotrace is defined, then the compiler will wrap all the calling code for Tracemessage
    • Because the first line of code defines a compiler called Dotrace, the compiler contains two calls to Tracemessage
#define DoTraceusing System;using System.Diagnostics;namespace AttributeConditional{    class Program    {        [Conditional("DoTrace")]        static void TraceMessage(string str)        {            Console.WriteLine(str);        }        static void Main()        {            TraceMessage("Start of Main");            Console.WriteLine("Doing work in Main.");            TraceMessage("End of Main");        }    }}


If you uncomment the first line to cancel the definition of dotrace, the compiler will no longer insert two calls to the Tracemessage code. This time, if we run the program, it will produce the following output:

Caller information attribute

The caller information attribute can access source code information such as the file path, the number of lines of code, the name of the calling member, and so on.

    • These three attribute names are CallerFilePath , CallerLineNumber andCallerMemberName
    • These attributes can only be used with optional parameters in the method

The following code declares a method named Mytrace that uses the three caller information attributes on three optional parameters. If these parameters are explicitly specified when the method is called, the true parameter value is used. However, when called in the Main method shown below, these values are not explicitly provided, so the system will provide the file path to the source code, the number of lines of code that called the method, and the name of the member that called the method.

using System;using System.Runtime.CompilerServices;public static class Program{    public static void MyTrace(string message,                                [CallerFilePath] string fileName="",                                [CallerLineNumber] int lineNumber=0,                                [CallerMemberName] string callingMember="")    {        Console.WriteLine("File:         {0}",fileName);        Console.WriteLine("Line:         {0}",lineNumber);        Console.WriteLine("Called From:  {0}",callingMember);        Console.WriteLine("Message:      {0}",message);    }    public static void Main()    {        MyTrace("Simple message");    }}

DebuggerStepThrough characteristics

When we step through the code, we often expect the debugger not to go into some way. We just want to execute the method and then continue to debug the next line. The DebuggerStepThrough feature tells the debugger not to go into this method debugging when executing the target code.
In my own code, this is the most commonly used feature. Some of the methods are small and undoubtedly correct, and they can only add to the annoyance when debugging a single step at a time. But use this feature with great care, because you don't want to exclude code that might contain bugs.
The following two points should be noted about DebuggerStepThrough:

    • The attribute is located in the System.Diagnostics namespace
    • This attribute can be used for classes, structs, constructors, methods, or accessors

The following piece of code, which is readily fabricated, uses this feature on an accessor and a method. You will find that the debugger does not enter the Incrementfields method or the set accessor of the X property when debugging this code.

using System;using System.Diagnostics;class Program{    int _x=1;    int X    {        get{return _x;}        [DebuggerStepThrough]        set        {            _x=_x*2;            _x+=value;        }    }    public int Y{get;set;}    public static void Main()    {        var p=new Program();        p.IncrementFields();        p.X=5;        Console.WriteLine("X = {0}, Y = {1}",p.X,p.Y);    }    [DebuggerStepThrough]    void IncrementFields()    {        X++;        Y++;    }}
Other pre-defined features

. The NET Framework has predefined features that many compilers and the CLR can understand and interpret, some of which are listed in the following table. A short name with no attribute suffix is used in the table. For example, the full name of CLSCompliant is CLSCompliantAttribute.

More about app Features

At this point, we demonstrate the simple use of attributes, all of which apply a single attribute to the method. This section will explain how other features are used.

Multiple features

We can apply multiple features to a single structure.

    • Multiple attributes can use any one of the following formats:
      • Separate feature fragments are stacked with each other
      • Single attribute fragment, separated by commas between attributes
    • We can list features in any order

For example, the following two code snippets show two ways to apply multiple attributes. The code for the two fragments is equivalent.

[Serializable ]                       //多层结构[MyAttribute("Simple class", "Version 3.57")][MyAttribute("Simple class", "Version 3.57"),Serializable]    //逗号分隔
Other types of targets

In addition to classes, we can also apply attributes to other program structures such as fields and properties. The following declaration shows the attributes on the field and several attributes on the method:

[MyAttribute("Holds a value", "Version 3.2")]    //字段上的特性public int MyField;[Obsolete]                                       //方法上的特性[MyAttribute("Prints out a message.", "Version 3.6")]public void Printout(){    …}

We can also explicitly label the attribute to apply it to a particular target structure. To use an explicit target, place the target type at the beginning of the attribute fragment followed by a colon. For example, the following code decorates the method with attributes, and also applies the attribute to the return value.

显式目标说明符   ↓[method: MyAttribute("Prints out a message.", "Version 3.6")][return: MyAttribute("This value represents …", "Version 2.3")]public long ReturnSetting(){    …}

As listed in the following table, the C # language defines the 10 standard attribute targets. Most target names are self-explanatory (self-explanatory), while type overrides classes, structs, delegates, enumerations, and interfaces. The Typevar target name specifies the type parameter that uses the generic structure.

Global attributes

We can also use explicit target specifiers to set attributes at the assembly or module level by using the assembly and module target names. (Assemblies and modules are explained in Chapter 21st.) Some key points about the assembly-level features are:

    • The attributes at the program level must be placed outside any namespace and are usually placed in the AssemblyInfo.cs file
    • AssemblyInfo.cs files typically contain metadata about companies, products, and copyright information

The following line of code is excerpted from the AssemblyInfo.cs file:

[assembly: AssemblyTitle("SuperWidget")][assembly: AssemblyDescription("Implements the SuperWidget product.")][assembly: AssemblyConfiguration("")][assembly: AssemblyCompany("McArthur Widgets, Inc.")][assembly: AssemblyProduct("Super Widget Deluxe")][assembly: AssemblyCopyright("Copyright ? McArthur Widgets 2012")][assembly: AssemblyTrademark("")][assembly: AssemblyCulture(")]
Custom Attributes

You may have noticed that the syntax for applying attributes is quite different from the other syntax you've seen before. You might think of a type that is completely different from the structure, not that the attribute is just a class of a particular type.
Some key points about attribute classes are as follows.

    • User-defined attribute classes are called custom attributes
    • All attribute classes are derived from System.Attribute
Declaring Custom Attributes

In general, declaring an attribute class is the same as declaring another class. However, there are a few things worth noting, as shown below.

    • To declare a custom attribute, you need to do the following
      • Declares a class that derives from System.Attribute
      • Give it a name after the end of the suffix attribute
    • For security reasons, it is generally recommended that you declare a sealed attribute class (sealed sealed class, cannot be inherited)

For example, the following code shows the starting part of the declaration of the Myattributeattribute attribute:

                         特性名                 基类                           ↓                     ↓public sealed class MyAttributeAttribute : System.Attribute{…}

Because the attribute holds the target information, the public members of all attribute classes can only be:

    • Field
    • Property
    • constructor function
constructors that use attributes

Attributes, like other classes, have constructors. Each attribute must have at least one public constructor.

    • As with other classes, if you do not declare constructors, the compiler generates an implicit, public, and parameterless constructor for us
    • The constructor of an attribute, like any other constructor, can be overloaded
    • The full name of the class must be used when declaring the constructor, including the suffix. We can only use short names when applying attributes

For example, if you have the following constructor (the name does not contain a suffix), the compiler generates an error message:

public MyAttributeAttribute(string desc,string ver){    Description=desc;    VersionNumber=ver;}
Specifying constructors

When we apply an attribute to a target, we are actually specifying which constructor should be used to create an instance of the attribute. The parameters listed in the attribute application are actually parameters of the constructor.
For example, in the following code, MyAttribute is applied to a field and a method. For a field, the declaration specifies a constructor that uses a single string. For a method, the declaration specifies a constructor that uses two strings.

[MyAttribute("Holds a value")]    //使用一个字符串的构造函数public int MyField;[MyAttribute("version 1.3", "Sal Martin")]    //使用两个字符串的构造函数public void MyMethod(){    …}

Other key points about attribute constructors are as follows.

    • When an attribute is applied, the constructor's argument must be a constant expression that can determine the value at compile time
    • If you apply an attribute constructor that has no parameters, you can omit the parentheses. For example, two classes of the following code use the parameterless constructor of the myattr attribute. The meaning of both forms is the same
[MyAttr]class SomeClass …[MyAttr()]class OtherClass …
Using constructors

As with other classes, we cannot explicitly call constructors. When an instance of an attribute is created, the constructor can only be called when the attribute's consumer access attribute. This is very different from the instances of other classes that are created where you use object creation expressions. Applying an attribute is a declaration statement, and it does not determine when an object of an attribute class is constructed.
The use of common class constructors and the use of constructors for their properties are compared.

    • The actual meaning of the command statement is: "Create a new class here"
    • The meaning of the declaration statement is: "This attribute is associated with this target, and if you need to construct the attribute, use this constructor"

Positional parameters and named parameters in constructors

And ordinary classes are similar to the method of construction, the method of constructing attributes can also use positional parameters and named parameters. The following code shows the use of a positional parameter and two named parameters to apply an attribute:

                   位置参数            命名参数              命名参数                      ↓                   ↓                   ↓[MyAttribute("An excellent class",Reviewer="Amy McArthur",Ver="0.7.15.33")]

The following code demonstrates the declaration of an attribute class and applies attributes to the MyClass class. Notice that the declaration of the constructor lists only one parameter, but we can give the constructor 3 arguments by naming the arguments. Two named parameters set the values of the fields ver and reviewer.

public sealed class MyAttributeAttribute : System.Attribute{    public string Description;    public string Ver;    public string Reviewer;    public MyAttributeAttribute(string desc) //一个形参    {        Description = desc;    }}             //三个实参[MyAttribute("An excellent class”, Reviewer="Amy McArthur", Ver="7.15.33")]class MyClass{    …}

Any positional arguments required by the constructor must be placed before the named arguments.

Restrictions on the use of features

We have seen the ability to apply attributes to a class. While the attribute itself is a class, there is an important pre-defined feature that can be applied to the custom attribute, which is the AttributeUsage feature. We can use it to restrict the use of attributes on a target type.
For example, if we want custom attribute myattribute to be applied only to methods, you can use AttributeUsage in the following form:

                   只针对方法                       ↓[AttributeUsage( AttributeTarget.Method )]public sealed class MyAttributeAttribute : System.Attribute{    …}

AttributeUsage has three important public properties, as shown in the following table. The table shows the meaning of the property name and property. For the latter two properties, their default values are also displayed.

the AttributeUsage constructor
The AttributeUsage constructor accepts a single positional parameter that specifies the type of target allowed for the attribute. It uses this parameter to set the Validon, and accepts that the target type is a member of the Attributetarget enumeration. The complete list of members for the Attributetarget enumeration is shown in the following table.
We can use a bitwise OR operator to combine the types. For example, in the following code, the decorated attribute can only be applied to methods and constructors.

                                     目标                                       ↓[AttributeUsage( AttributeTarget.Method| AttributeTarget.Constructor )]public sealed class MyAttributeAttribute : System.Attribute


When we apply AttributeUsage to an attribute declaration, the constructor requires at least one parameter, and the target type that the parameter contains is saved in Validon. We can also selectively set the inherited and AllowMultiple properties by using named parameters. If we do not set them, they will remain the default values shown in table 24-4.
As an example, the following section of code specifies the following aspects of MyAttribute.

    • MyAttribute can and can only be applied to the class
    • MyAttribute is not inherited by derived classes to which it is applied
    • Cannot have multiple instances of MyAttribute applied to the same target
[AttributeUsage( AttributeTarget.Class,     //必需的,位置参数                Inherited = false,          //可选的,命名参数                AllowMultiple = false )]    //可选的,命名参数public sealed class MyAttributeAttribute : System.Attribute{    …}
Best Practices for custom attributes

It is highly recommended to use the following practices when writing custom features.

    • Attribute classes should represent some state of the target structure
    • If the attribute requires some fields, you can collect the data by including a constructor with positional parameters, and optional fields can be initialized on demand with named arguments
    • Do not implement public methods or other function members in addition to attributes
    • To be more secure, declare the attribute class as sealed
    • To explicitly specify an attribute target group by using AttributeUsage in an attribute declaration

These guidelines are demonstrated in the following code:

[AttributeUsage( AttributeTargets.Class )]public sealed class ReviewCommentAttribute : System.Attribute{    public    string    Description     {get;set;}    public    string    VersionNumber   {get;set;}    public    string    ReviewerID      {get;set;}    public ReviewCommentAttribute(string desc, string ver)    {        Description = desc;        VersionNumber = ver;    }}
Access attributes

At the beginning of this chapter, we have seen that you can use the type object to get types information. We can also do this for access to custom attributes. The two methods of type (isdefined and getcustomattributes) are very useful here.

Using the IsDefined method

We can use the IsDefined method of the type object to detect whether an attribute is applied to a class.
For example, the following code declares an attribute class MyClass, and the consumer that acts as its own attribute accesses the declared and applied attributes in the program. At the beginning of the code is the declaration of the MyAttribute attribute and the MyClass class that applies the attribute. This piece of code does the following things.

    • First, main creates an object of the class. A reference to the type object is then obtained by using the GetType method inherited from the object base class
    • With a reference to the type object, you can call the IsDefined method to determine whether the Reviewcomment attribute is applied to this class
      • The first parameter accepts the type object that requires the feature to be checked
      • The second argument is of type bool, which indicates whether to search for MyClass's inheritance tree to find this attribute
[AttributeUsage(AttributeTargets.Class)]public sealed class ReviewCommentAttribute:System.Attribute{…}[ReviewComment("Check it out","2.4")]class MyClass{}class Program{    static void Main()    {        var mc=new MyClass();        Type t=mc.GetType();        bool isDefined=            t.IsDefined(typeof(ReviewCommentAttribute),false);        if(isDefined)            Console.WriteLine("ReviewComment is applied to type {0}",t.Name);    }}

Using the GetCustomAttributes method

The GetCustomAttributes method returns an array of attributes that are applied to the structure.

    • The object that actually returns 冋 is an array of object, so we must cast it to the corresponding attribute type
    • Boolean parameter specifies whether to search the inheritance tree for features
    • object[] AttArr = t.GetCustomAttributes(false);
    • Once the GetCustomAttributes method is called, each instance of the attribute associated with the target is created

The following code uses the same attributes and class declarations as in the previous example. However, in this case, it does not detect whether the attribute is applied to the class, but rather gets an array of attributes applied to the class, and then iterates through them, outputting the values of their members.

using System;[AttributeUsage(AttributeTargets.Class)]public sealed class MyAttributeAttribute:System.Attribute{    public  string  Description  {get;set;}    public  string  VersionNumber{get;set;}    public  string  ReviewerID   {get;set;}    public MyAttributeAttribute(string desc,string ver)    {        Description=desc;        VersionNumber=ver;    }}[MyAttribute("Check it out","2.4")]class MyClass{}class Program{    static void Main()    {        Type t=typeof(MyClass);        object[] AttArr=t.GetCustomAttributes(false);        foreach(Attribute a in AttArr)        {            var attr=a as MyAttributeAttribute;            if(null!=attr)            {                Console.WriteLine("Description    :{0}",attr.Description);                Console.WriteLine("Version Number :{0}",attr.VersionNumber);                Console.WriteLine("Reviewer ID    :{0}",attr.ReviewerID);            }        }    }}

C # schematic tutorial 24th reflections and Features

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.