Negative tive C # principle 43: Do not abuse reflection)

Source: Internet
Author: User

Negative tive C # principle 43: Do not abuse reflection
Item 43: Don't overuse reflection

When creating a binary component, it also means that you need to use late binding and reflection to find what you need with Special FunctionsCode. Reflection is a powerful tool that allows you to write software that can be dynamically configured. Use reflection, an applicationProgramYou can add new components to update functions, which are not available at the beginning of the software release. This is advantageous.

This scalability also brings about some complicated problems, and the increase of complicated problems will increase the possibility of other problems. When you use reflection, you are centered around the C # security type. However, the parameters and returned values of a member call exist in the system. Object type. You must ensure that these types are correct at runtime. To put it simply, using reflection can make it easy to create dynamic programs, but it also makes it easy to make program errors. Generally, you can create a series of interface sets to minimize or remove reflection. These interface sets should express your assumptions about the type.

The reflection gives you the ability to create type instances, call Member methods on objects, and access member data on objects. This sounds the same as everyday programming tasks. Indeed, there is no novelty in reflection: It is to dynamically create other binary components. In most cases, you do not need scaling functions like reflection, because there are other options that are easier to maintain.

Let's start from creating a given type of instance. You can often use a class factory to complete the same task. Consider the following code. It creates a mytype instance by calling the default constructor through reflection:

// Usage: Create a new object Using Reflection:
Type T = typeof (mytype );
Mytype OBJ = newinstance (T) as mytype;

// Example factory function, based on reflection:
Object newinstance (type T)
{
// Find the default constructor:
Constructorinfo CI = T. getconstructor (New Type [0]);
If (CI! = NULL)
// Invoke default constructor, and return
// The new object.
Return CI. Invoke (null );

// If it failed, return null.
Return NULL;
}

The Code detects the type through reflection and calls the default constructor to create an object. If you need to create a type instance that does not know any information in advance at runtime, this is the only choice. This is a piece of fragile code, which is lazy than the existence of Default constructors. In addition, you can still compile the mytype default constructor after removing it. You must complete detection at runtime and capture any possible exceptions. A factory function that completes the same function cannot be compiled when the constructor is removed:

Public mytype newinstance ()
{
Return new mytype ();
}

In fact, vs. NET will add default constructor to us, so the above two methods can be compiled and run correctly. I did a test. However, if you add access restrictions to the constructor, you can make the class factory unable to construct the object and generate a compile-time error .)

You should use static classfactory functions to replace the method for creating instances that are too lazy to reflect. If you need instance objects to bind data later, you should use factory functions and use relevant features to mark them (see Principle 42 ).

Another potential use of reflection is access type members. You can use the member name and type to call the actual function at runtime:

// Example usage:
Dispatcher. invokemethod (anobject, "myhelperfunc ");

// Dispatcher invoke method:
Public void invokemethod (Object o, string name)
{
// Find the member functions with that name.
Memberinfo [] mymembers = O. GetType (). getmember (name );
Foreach (methodinfo m in mymembers)
{
// Make sure the parameter list matches:
If (M. getparameters (). Length = 0)
// Invoke:
M. Invoke (O, null );
}
}

In the above Code, the error is avoided by the screen. If the type name is incorrect, this method cannot be found. No method is called.

This is just a simple example. To create a flexible invokemethod version, you must check all the parameter types from the parameter list returned by the getparameters () method. This kind of code is so long and so bad that I don't want to waste any time trying to demonstrate it.

The third use of reflection is to access data members. The code is similar to accessing a member function:

// Example usage:
Object field = dispatcher. retrievefield (anobject, "myfield ");

// Elsewhere in the dispatcher class:
Public object retrievefield (Object o, string name)
{
// Find the field.
Fieldinfo myfield = O. GetType (). getfield (name );
If (myfield! = NULL)
Return myfield. getvalue (O );
Else
Return NULL;
}

Like method calls, reflection is used to retrieve a data member. You need to call the type query by name on a field to check whether it matches the requested field name. If one is found, you can use the fieldinfo structure to return the value. This structure is very common in the. NET Framework. Data Binding is to use reflection to find the attributes marked with the binding operation. In this case, the dynamic nature of data binding exceeds its overhead. It is worth using reflection for dynamic binding .)

Therefore, if reflection is so painful, you need to find a better and simpler alternative. You have three options: first, use interfaces. You can define interfaces for any expected class or structure (see Principle 19 ). This may replace all reflection codes with clearer code:

Imyinterface Foo = OBJ as imyinterface;
If (foo! = NULL)
{
Foo. dowork ();
Foo. MSG = "Work is done .";
}

If you use a factory-class function that marks a feature to merge interfaces, almost all of the solutions you want to reflect become simpler:

Public class mytype: imyinterface
{
[Factoryfunction]
Public static imyinterface
Createinstance ()
{
Return new mytype ();
}

# Region imyinterface
Public String msg
{
Get
{
Return _ MSG;
}
Set
{
_ MSG = value;
}
}
Public void dowork ()
{
// Details elided.
}
# Endregion
}

Compare this code with the previous reflection-based solution. Even if this is just a simple example, there are some other highlights when using all reflection APIs for some weak types: The return type is already a typed object. In reflection, if you want to obtain the correct type, you need to force conversion. This operation may fail and may be dangerous in inheritance. When using interfaces, the strong type detection provided by the compiler is clearer and easier to maintain.
Reflection should be used only when some call targets cannot be clearly expressed using interfaces .. NET data binding can work on any public attribute of the type. limiting it to the defined interface may greatly limit its use. The menu handle example allows any function (either instance or static) to implement the command handle. Using an interface also limits these functions to only instance methods. Both fxcop and nunit (see principle 48) have extended the use of reflection because the actual problems they encounter are best handled with it. Fxcopy checks all codes to determine if they are in conflict with existing principles. Reflection is required. Nunit must call your compiled test code. It uses reflection to determine which code you have written requires unit testing. The test code you may want to write may be a set of methods, but the interfaces cannot express them. Nunit uses features to discover tests and test cases to make it easier (see Principle 42 ).

When you can use interfaces to plan the methods and attributes you want to call, you can have a system that is clearer and easier to maintain. Reflection is a powerful tool for binding data later .. Net Framework uses it to bind data between Windows controls and Web controls. However, in many general cases, code is rarely used. Instead, code is created using a class factory, delegate, and interface, which can generate a system that is easier to maintain.

====================

item 43: Don't overuse reflection
building binary components sometimes means utilizing late binding and reflection to find the code with the participating functionality you need. reflection is a powerful tool, and it enables you to write software that is much more dynamic. using Reflection, an application can be upgraded with new capabilities by adding new components that were not available when the application was deployed. that's the upside.

with this flexibility comes increased complexity, and with increased complexity comes increased chance for each problems. when you use reflection, you circumvent C #'s type safety. instead, the invoke members use parameters and return values typed as system. object. you must make sure the proper types are used at runtime. in short, using reflection makes it much easier to build dynamic programs, but it is also much easier to build broken programs. often, with a little thought, you can minimize or remove the need for reflection by creating a set of interface definitions that express your assumptions about a type.

Reflection gives you the capability to create instances of objects, invoke members on those objects, and access data members in those objects. those sound like normal everyday programming tasks. they are. there is nothing magic about reflection: it is a means of dynamically interacting with other binary components. in most cases, you don't need the flexibility of reflection because other alternatives are more maintainable.

Let's begin with creating instances of a given type. you can often accomplish the same result using a class factory. consider this code fragment, which creates an instance of mytype by calling the default constructor Using Reflection:

// Usage: Create a new object Using Reflection:
Type T = typeof (mytype );
Mytype OBJ = newinstance (T) as mytype;

// Example factory function, based on reflection:
Object newinstance (type T)
{
// Find the default constructor:
Constructorinfo CI = T. getconstructor (New Type [0]);
If (CI! = NULL)
// Invoke default constructor, and return
// The new object.
Return CI. Invoke (null );

// If it failed, return null.
Return NULL;
}

 

The Code examines the Type Using Reflection and invokes the default constructor to create the object. if you need to create a type at runtime without any previous knowledge of the type, this is the only option. this is brittle code that relies on the presence of a default constructor. it still compiles if you remove the default constructor from mytype. you must perform Runtime Testing to catch any problems that arise. A class factory function that runs med the same operations wocould not compile if the default constructor was removed:

Public mytype newinstance ()
{
Return new mytype ();
}

 

You shoshould create static factory functions instead of relying on reflection to instantiate objects. if you need to instantiate objects using late binding, create factory functions and tag them as such with attributes (see item 42 ).

Another potential use of reflection is to access members of a type. You can use the member name and the type to call a particle function at runtime:

// Example usage:
Dispatcher. invokemethod (anobject, "myhelperfunc ");

// Dispatcher invoke method:
Public void invokemethod (Object o, string name)
{
// Find the member functions with that name.
Memberinfo [] mymembers = O. GetType (). getmember (name );
Foreach (methodinfo m in mymembers)
{
// Make sure the parameter list matches:
If (M. getparameters (). Length = 0)
// Invoke:
M. Invoke (O, null );
}
}

 

Runtime errors are lurking in the previous Code. If the name is typed wrong, the method won't be found. No method will be called.

It's also a simple example. creating a more robust version of invokemethod wocould need to check the types of all proposed parameters against the list of all parameters returned by the getparameters () method. that code is lengthy enough and uugly enough that I did not even want to waste the space to show it to you. it's that bad.

The third use of reflection is accessing data members. The code is similar to accessing member functions:

// Example usage:
Object field = dispatcher. retrievefield (anobject, "myfield ");

// Elsewhere in the dispatcher class:
Public object retrievefield (Object o, string name)
{
// Find the field.
Fieldinfo myfield = O. GetType (). getfield (name );
If (myfield! = NULL)
Return myfield. getvalue (O );
Else
Return NULL;
}

 

As with the method invocation, using reflection to retrieve a data member involves querying the type for a field with a name that matches the requested field. if one is found, the value can be retrieved using the fieldinfo structure. this construct is rather common in the framework. databinding makes use of reflection to find the properties that are the targets of binding operation. in those cases, the dynamic nature of data binding outweighs the possible costs.

So, if reflection is such a painful process, you need to look for better and simpler alternatives. you have three options. the first is interfaces. you can define interfaces for any contract that you have CT classes or structs to implement (see item 19 ). that wowould replace all the reflection code with a few far clearer lines of code:

Imyinterface Foo = OBJ as imyinterface;
If (foo! = NULL)
{
Foo. dowork ();
Foo. MSG = "Work is done .";
}

 

If you combine interfaces with a factory function tagged with an attribute, almost any system you thought deserved a solution based on reflection gets much more simple:

Public class mytype: imyinterface
{
[Factoryfunction]
Public static imyinterface
Createinstance ()
{
Return new mytype ();
}

# Region imyinterface
Public String msg
{
Get
{
Return _ MSG;
}
Set
{
_ MSG = value;
}
}
Public void dowork ()
{
// Details elided.
}
# Endregion
}

 

contrast this code with the reflection-based solution shown earlier. even these simple examples have glossed over some of the weakly typed issues common to all the reflection APIs: The return values are all typed as objects. if you want to get the proper type, you need to cast or convert the type. those operations cocould fail and are inherently dangerous. the strong type checking that the compiler provides when you create interfaces is much clearer and more maintainable.

reflection shocould be used only when the invocation target can't be cleanly expressed using an interface .. NET data binding works with any public property of a type. limiting it to an interface definition wowould greatly limit its reach. the menu handler sample allows any function (either instance or static) to implement the command handler. using an interface wowould limit that functionality to instance methods only. both fxcop and nunit (see item 48) make extensive use of reflection. they use reflection because the nature of the problems they address are best handled using it. fxcopy examines all your code to evaluate it against a set of known rules. that requires reflection. nunit must call test code you 've written. it uses reflection to determine what code you 've written to unit test your code. an interface cannot express the full set of methods used to test any code you might write. nunit does use attributes to find tests and test cases to make its job easier (see item 42 ).

when you can factor out the methods or properties that you intend to invoke using interfaces, you'll have a cleaner, more maintainable system. reflection is a powerful late-binding mechanic. the. net Framework uses it to implement data binding for both Windows-and web-based controls. however, in less general uses, creating code using class factories, delegates, and interfaces will produce more Maintainable Systems.

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.