Moderate
C # Is a static type language. To use a type, you must reference the definition of this type. Therefore, reference dependencies between components may occur from the software organization perspective. There are two common reference dependency modes:
A. Positive dependency: component A uses the class T defined in component B. component A directly references component B. The dependency is "component A-> component B ".
B. reverse dependency: component A defines functional specifications through interface I for abstract programming. Component B references component A in turn and defines class T to implement interface I; the dependency between I and T is "component A <-component B ". This is the famous IoC method.
To put it simply, IoC is "Whoever makes a specification, who has control; who executes the specification, who is controlled ". If the specification relies on the static type check of C #, such as an interface or abstract class, the specification has A strong syntax constraint, making the compilation of component A relatively independent, component B is subject to component.
The first article in this series provides an interface-based IoC example. We can see that when a third-party component is required, in order to apply the static type constraints of the interface, you have to add an adapter to implement the interface and package the call to third-party components. This shows that the interface-based IoC is not flexible in bonding specifications and implementation.
However, the specification and type constraints are not necessarily linked. In the Delegate-based IoC example, we can easily bond specifications and implementations without any adapter, and demonstrate great flexibility. This means that the delegated definition specification will not cause component B's dependency on component A, and the implementation of component A and component B is relatively independent.
In fact, we can also have more flexible normative expressions than delegation, such as expressing norms through HTTP + XML, which is even language-independent, it is entirely possible that component A is written by C #, and component B is written by Java.
The three standardized definition methods listed above: Based on interfaces, based on delegation, and based on HTTP + XML, represent three styles from constraints to protocols, from strict to flexible. Of course, there are more methods, but here we only list these three methods as the representative. The dynamic and static interfaces must be controlled in a specific sense, and the interface is too rigid. The HTTP + XML method is completely based on the runtime protocol, and you need to perform a lot of checks on your own; the advantage of delegation is that it not only eliminates the dependencies between components A and B, but also enjoys the advantages of IDE smart prompts and compiler checks (signature checks. Therefore, entrusting is a moderate way to combine dynamic and static.
Duck Typing
However, it is a pity that delegation cannot cover all functions of interfaces or classes. Some friends mentioned that "interfaces are the abstraction of object functions, while delegation is the abstraction of method functions. Then we will naturally wonder if there is a way to apply the idea of delegation to objects? Yes! It is duck typing. As mentioned above, duck typing focuses on "What can an object" or "how to use an object". It does not matter what classes an object inherits from or what interfaces It implements. The meaning of duck typing is "if an animal walks up like a duck and calls it like a duck, I can treat it as a duck ". In contrast to the inheritance polymorphism, duck typing can achieve non-inheritance polymorphism. According to the intention of duck typing, the pure duck typing should look like this:
----------------------------------------------
Static void Main (string [] args)
{
Object person = new Person ();
IPerson duck = Duck. Cast <IPerson> (person); // create a duck object
Console. WriteLine (duck. Name + "will be" + (duck. Age + 1) + "next year ");
Duck. Play ("basketball ");
Console. WriteLine (duck. Mother); // null
// Duck cannot call duck. Sing ()
}
Interface IPerson
{
String Name {get ;}
Int Age {get ;}
String Mother {get ;}
Void Play (string ball );
}
Class Person
{
Public string Name {get {return "Todd ";}}
Public int Age {get {return 26 ;}}
Public void Play (string ball) {Console. WriteLine ("Play" + ball );}
Public void Sing (string song) {Console. WriteLine ("Sing" + song ");}
}
----------------------------------------------
In the above example, although the person object does not implement the IPerson interface, we can also use Duck. Cast <IPerson> (person) to create a Duck object to call the attributes and methods of person. This method of binding interfaces and objects is very similar to that of delegation and methods, which truly achieves our idea of applying the idea of delegation to objects.
In C #, Duck must be implemented. the Cast <T> function allows you to dynamically create a proxy class that implements the T interface through Emit, intercept method calls in the proxy class, and convert method calls to reflection calls on the target object. DynamicProxy of the Castle open-source project is a very useful tool. With its help, it is easy to create proxy classes and intercept method calls.
Dynamic type
In fact, duck typing is a concept of dynamic type. C #4.0 has implemented dynamic types through the dynamic keyword. Let's take a look at the following example:
----------------------------------------------
String json = @ "{" "FirstName": "" John "", "" LastName ":" "Smith" "," Age "": 21 }";
Dynamic person = CreateFromJson (json );
Console. WriteLine ("{0} will be {1} next year", person. FirstName, person. Age + 1 );
Console. WriteLine (person. ToJson ());
Person. Play ("basketball"); // a method that does not exist. You can compile the method, but it throws an exception during running.
----------------------------------------------
With the dynamic keyword, we do not need to specify the type for the person object during compilation. The Compiler does not check the type, but converts the attribute access and method call of the object to reflection call. Therefore, as long as the object runtime type can be found through reflection matching attributes or methods.
The above example creates a dynamic object through json, which is as convenient as json operations in javascript. At runtime, person. firstName and person. age can correctly perform attribute access through reflection, person. toJson () can also be correctly executed, but person. play ("basketball") throws an exception because the method does not exist in the runtime type.
How does C #4.0 taste? Nice? But to be honest, I feel a little uncomfortable! Think about it, like an interface, like a delegate, or more like HTTP + XML? For dynamic objects, the compiler does not check the object type, the attribute type, or the method signature. Obviously, it is like HTTP + XML. It is completely based on the runtime protocol, and there is nothing static. The ideal way is to do not check the object type, but check the property type and method signature, as shown below:
----------------------------------------------
String json = @ "{" "FirstName": "" John "", "" LastName ":" "Smith" "," Age "": 21 }";
Dynamic person = CreateFromJson (json );
Console. WriteLine ("{0} will be {1} next year", person. FirstName <string>, person. Age <int> + 1 );
Console. WriteLine (person. ToJson <string> ());
Person. Play <string> ("basketball ");
String firstName = person. FirstName <string>;
Int age = person. Age <int>;
Func <string> toJson = person. ToJson <Func <string>;
Action <string> play = person. Play <Action <string>;
----------------------------------------------
In this way, except that the attribute and method names are dynamic, the signature of the attribute type and method is static, minimizing the possibility of running errors, and enjoying the benefits of static checks. In fact, along this line of thinking, we don't have to wait for C #4.0 dynamic to start the dynamic type. In the C #2.0 era, we can also do this:
----------------------------------------------
Object jsonObj = CreateFromJson (@ "{" "FirstName" ":" "John" "," "LastName": "" Smith "", "" Age "": 21 }");
Dynamic person = new Dynamic (jsonObject );
String firstName = person. Property <string> ("FirstName ");
Int age = person. Property <int> ("Age ");
Func <string> toJson = person. Method <Func <string> ("ToJson ");
Action <string> play = person. Method <Action <string> ("Play ");
----------------------------------------------
Here, I believe you will understand how to implement the Dynamic class? If you find it useful, try it yourself!
Follow-up
The next article will continue to explore how to simulate dynamic types in C #. Stay tuned!
Modification history
Clarified the concept of duck typing, supplemented by examples