美文网首页
70-483.C2O5-7.Create and use typ

70-483.C2O5-7.Create and use typ

作者: 小镭Ra | 来源:发表于2018-10-29 12:05 被阅读17次

Note Links

Objective 2.5: Find, execute, and create types at runtime by using reflection

Metadata is information about data. An attribute is one type of metadata that can be stored in a .NET application. Reflection is the
process of retrieving this metadata at runtime.

Creating and using attributes

Attributes can be added to all kinds of types: assemblies, types, methods, parameters, and properties.

Applying attributes

LISTING 2-58 Applying an attribute

[Serializable] 
class Person 
{ 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
}

When using the attribute, you can skip the Attribute suffix.

A type can have as many attributes applied to it as necessary. Some attributes can even be applied multiple times.

LISTING 2-59 Using multiple attributes

[Conditional("CONDITION1"), Conditional("CONDITION2")] 
static void MyMethod(){ }

An attribute also has a specific target to which it applies. It can be an attribute applied to a whole assembly, a class, a specific method, or even a parameter of a method.

LISTING 2-60 Specifying the target of an attribute explicitly

[assembly: AssemblyTitle("ClassLibrary1")] 
[assembly: AssemblyDescription("")] 
[assembly: AssemblyConfiguration("")] 
[assembly: AssemblyCompany("")] 
[assembly: AssemblyProduct("ClassLibrary1")] 
[assembly: AssemblyCopyright("Copyright ©  2013")] 
[assembly: AssemblyTrademark("")] 
[assembly: AssemblyCulture("")]

Reading attributes

LISTING 2-61 Seeing whether an attribute is defined

[Serializable] 
class Person { } 
if (Attribute.IsDefined(typeof(Person), typeof(SerializableAttribute))) { }

LISTING 2-62 Getting a specific attribute instance

ConditionalAttribute conditionalAttribute =  
    (ConditionalAttribute)Attribute.GetCustomAttribute( 
    typeof(ConditionalClass),  
    typeof(ConditionalAttribute)); 
string condition = conditionalAttribute.ConditionString; // returns CONDITION1

Creating custom attributes

A custom attribute class has to derive from System.Attribute (directly or indirectly).

LISTING 2-63 Using a category attribute in xUnit

[Fact] 
[Trait("Category", "Unit Test")] 
public void MyUnitTest() 
{ }
[Fact] 
[Trait("Category", "Integration Test")] 
public void MyIntegrationTest() 
{ }

LISTING 2-64 Creating a custom attribute

public class CategoryAttribute : TraitAttribute 
{ 
    public CategoryAttribute(string value) 
        : base("Category", value)  
    { } 
}
public class UnitTestAttribute : CategoryAttribute 
{ 
    public UnitTestAttribute() 
        : base("Unit Test")  
    { } 
}

LISTING 2-65 Using a custom attribute

[Fact] 
[UnitTest] 
public void MySecondUnitTest() 
{}

LISTING 2-66 Defining the targets for a custom attribute

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] 
public class MyMethodAndParameterAttribute : Attribute { }

LISTING 2-67 Setting the AllowMultiple parameter for a custom attribute

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 
class MyMultipleUsageAttribute : Attribute{ }

Attributes require all properties to be read-write.

LISTING 2-68 Adding properties to a custom attribute

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple=true)] 
class CompleteCustomAttribute : Attribute 
{ 
    public CompleteCustomAttribute(string description) 
    { 
        Description = description; 
    } 
    public string Description { get; set; } 
}

Using reflection

A basic example of reflection:

int i = 42; 
System.Type type = i.GetType();

MORE INFO ABOUT MANAGED EXTENSIBILITY FRAMEWORK

LISTING 2-69 Creating an interface that can be found through reflection

public interface IPlugin 
{ 
    string Name { get; } 
    string Description { get; } 
    bool Load(MyApplication application); 
}

LISTING 2-70 Creating a custom plug-in class

public class MyPlugin : IPlugin 
{ 
    public string Name 
    { 
        get { return "MyPlugin"; } 
    } 
 
    public string Description 
    { 
        get { return "My Sample Plugin"; } 
    }
    public bool Load(MyApplication application) 
    { 
        return true;             
    } 
}

LISTING 2-71 Inspecting an assembly for types that implement a custom interface

Assembly pluginAssembly = Assembly.Load("assemblyname"); 
 
var plugins = from type in pluginAssembly.GetTypes() 
              where typeof(IPlugin).IsAssignableFrom(type) && !type.IsInterface 
              select type;
foreach (Type pluginType in plugins) 
{ 
    IPlugin plugin = Activator.CreateInstance(pluginType) as IPlugin; 
}

The first line loads the assembly by name. One thing to note is that if you call this multiple times, the runtime will load the assembly only once. If you want to reload the assembly you would have to restart your application.

LISTING 2-72 Getting the value of a field through reflection

static void DumpObject(object obj) 
{ 
    FieldInfo[] fields = obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic); 
 
    foreach (FieldInfo field in fields) 
    { 
        if (field.FieldType == typeof(int)) 
        { 
            Console.WriteLine(field.GetValue(obj)); 
        } 
    } 
}

LISTING 2-73 Executing a method through reflection

int i = 42; 
MethodInfo compareToMethod = i.GetType().GetMethod("CompareTo", 
    new Type[] { typeof(int) }); 
int result = (int)compareToMethod.Invoke(i, new object[] { 41 });

Using CodeDom and lambda expressions to generate code

You can use the CodeDOM to create an object graph that can be converted to a source file or to a binary assembly that can be executed.

Every time you create the same code over and over with some slight modifications, you can look into the CodeDOM to automate the process.

LISTING 2-74 Generating "Hello World!" with the CodeDOM

CodeCompileUnit compileUnit = new CodeCompileUnit(); 
CodeNamespace myNamespace= new CodeNamespace("MyNamespace"); 
myNamespace.Imports.Add(new CodeNamespaceImport("System"));  
CodeTypeDeclaration myClass = new CodeTypeDeclaration("MyClass"); 
CodeEntryPointMethod start = new CodeEntryPointMethod(); 
CodeMethodInvokeExpression cs1 = new CodeMethodInvokeExpression( 
    new CodeTypeReferenceExpression("Console"), 
    "WriteLine", new CodePrimitiveExpression("Hello World!")); 
 
compileUnit.Namespaces.Add(myNamespace); 
myNamespace.Types.Add(myClass); 
myClass.Members.Add(start); 
start.Statements.Add(cs1);

LISTING 2-75 Generating a source file from a CodeCompileUnit

CSharpCodeProvider provider = new CSharpCodeProvider();
using (StreamWriter sw = new StreamWriter("HelloWorld.cs", false)) 
{ 
    IndentedTextWriter tw = new IndentedTextWriter(sw, "    "); 
    provider.GenerateCodeFromCompileUnit(compileUnit, tw, 
        new CodeGeneratorOptions()); 
    tw.Close(); 
}

LISTING 2-76 The automatically generated source file

//------------------------------------------------------------------------------ 
// <auto-generated> 
//     This code was generated by a tool. 
//     Runtime Version:4.0.30319.18010 
// 
//     Changes to this file may cause incorrect behavior and will be lost if 
//     the code is regenerated. 
// </auto-generated> 
//------------------------------------------------------------------------------ 
namespace MyNamespace { 
    using System; 
 
    public class MyClass {         
        public static void Main() { 
            Console.WriteLine("Hello World!"); 
        } 
    } 
}

Lambda expressions

Anonymous Methods (C# Programming Guide)

LISTING 2-77 Creating a Func type with a lambda

Func<int, int, int> addFunc = (x, y) => x + y; 
Console.WriteLine(addFunc(2, 3));

The strange => notation can be read as "becomes" or "for
which." The addFunc type can be read as "x, y become x + y".

Expression trees

When using lambdas, you will come across expression trees, which are representations of code in a tree-like data structure.

LISTING 2-78 Creating "Hello World!" with an expression tree

BlockExpression blockExpr = Expression.Block( 
 Expression.Call( 
      null, 
      typeof(Console).GetMethod("Write", new Type[] { typeof(String) }), 
      Expression.Constant("Hello ") 
     ), 
  Expression.Call( 
      null, 
      typeof(Console).GetMethod("WriteLine", new Type[] { typeof(String) }), 
      Expression.Constant("World!") 
      ) 
 ); 
 
Expression.Lambda<Action>(blockExpr).Compile()();

Objective summary

  • A C# assembly stores both code and metadata.
  • Attributes are a type of metadata that can be applied in code and queried at runtime.
  • Reflection is the process of inspecting the metadata of a C# application.
  • Through reflection you can create types, call methods, read properties, and so forth.
  • The CodeDOM can be used to create a compilation unit at runtime. It can be compiled or converted to a source file.
  • Expression trees describe a piece of code. They can be translated to something else (for example, SQL) or they can be compiled and executed.

Objective 2.6: Manage the object life cycle

Understanding garbage collection

The stack keeps track of what's executing in your code, and the heap keeps track of your objects.

The stack is automatically cleared at the end of a method. The CLR takes care of this and you don't have to worry about it. The heap is managed by the garbage collector.

The garbage collector works with a mark and compact algorithm. The mark phase of a collection checks which items on the heap are still being referenced by a root item. A root can be a static field, a method parameter, a local variable, or a CPU register. If the garbage collector finds a "living" item on the heap, it marks the item. After checking the whole heap, the compact operation starts. The garbage collector then moves all living heap objects close together and frees the memory for all other objects.

For doing this, the garbage collector has to make sure that no state is changing while performing all the marking and compacting. Because of this, all threads are frozen while doing a collect operation. It also has to make sure that all references to living objects are correct. After moving objects around, the garbage collector will fix all existing references to objects.

The garbage collector starts cleaning up only when there is not enough room on the heap to construct a new object (or when Windows signals that it's low on memory).

When garbage collection does start, it collects only Generation 0. When executing a cleanup, items that survive (because they are still being referenced) are promoted to a higher generation. They are longer-living objects and the garbage collector makes the assumption that longer-living objects will probably stay around for some time. Because of this, the garbage collector focuses on the objects in Generation 0. They are just created and will probably be unnecessary in a small amount of time. The other generations will be touched only when the garbage collector cannot free enough memory by cleaning
Generation 0.

Managing unmanaged resources

Finalization is a mechanism that allows a type to clean up prior to garbage collection.

LISTING 2-79 Adding a finalizer

public class SomeType 
{ 
    ~SomeType() 
    {  
        // This code is called when the finalize method executes 
    } 
}

LISTING 2-80 Not closing a file will throw an error

StreamWriter stream = File.CreateText("temp.dat"); 
stream.Write("some data"); 
 
File.Delete("temp.dat"); // Throws an IOException because the file is already open.

LISTING 2-81 Forcing a garbage collection

StreamWriter stream = File.CreateText("temp.dat"); 
stream.Write("some data"); 
GC.Collect(); 
GC.WaitForPendingFinalizers(); 
File.Delete("temp.dat");

The line WaitForPendingFinalizers makes sure that all finalizers have run before the code continues. The garbage collector is pretty smart in managing memory, and it's not recommended that you call GC.Collect yourself.

When running this piece of code in Release mode, the garbage collector will see that there are no more references to stream, and it will free any memory associated with the StreamWriter instance. This will run the finalizer, which in turn will release any file handles to the temp.dat file (in debug mode, the compiler will make sure that the reference isn't garbage collected till the end of the method).

A finalizer increases the life of an object. Because the finalization code also has to run, the .NET Framework keeps a reference to the object in a special finalization queue. An additional thread runs all the finalizers at a time deemed appropriate based on the execution context. This delays garbage collection for types that have a finalizer.

The IDiposable interface offers one method: Dispose, which will free any unmanaged resources immediately.

LISTING 2-83 Calling Dispose to free unmanaged resources

StreamWriter stream = File.CreateText("temp.dat"); 
stream.Write("some data"); 
stream.Dispose(); 
File.Delete("temp.dat");

The using statement is translated by the compiler in a try/finally statement that calls Dispose on the object. Because of this, the using statement can be used only with types that implement IDisposable.

using (StreamWriter sw = File.CreateText("temp.dat")) { }

Implementing IDisposable and a finalizer

LISTING 2-84 Implementing IDisposable and a finalizer

using System; 
using System.IO; 
class UnmanagedWrapper : IDisposable 
{ 
    public FileStream Stream { get; private set; } 
 
    public UnmanagedWrapper() 
    { 
        this.Stream = File.Open("temp.dat", FileMode.Create); 
    }
    ~UnmanagedWrapper() 
    { 
        Dispose(false); 
    }
    public void Close() 
    { 
        Dispose(); 
    }
    public void Dispose() 
    { 
        Dispose(true); 
        System.GC.SuppressFinalize(this); 
    }
    public void Dispose(bool disposing) 
    { 
        if (disposing) 
        { 
            if (Stream != null) 
            { 
                Stream.Close(); 
            } 
        } 
    } 
}

NOTICE:

  1. The finalizer only calls Dispose passing false for disposing.
  2. The extra Dispose method with the Boolean argument does the real work. This method checks if it's being called in an explicit Dispose or if it's being called from the finalizer:
  3. If the finalizer calls Dispose, you do nothing because the Stream object also implements a finalizer, and the garbage collector takes care of calling the finalizer of the Stream instance. You can't be sure if it's already called, so it's best to leave it up to the garbage collector.
  4. If Dispose is called explicitly, you close the underlying FIleStream. It's important to be defensive in coding this method; always check for any source of possible exceptions. It could be that Dispose is called multiple times and that shouldn't cause any errors.
  5. The regular Dispose method calls GC.SuppressFinalize(this) to make sure that the object is removed from the finalization list that the garbage collector is keeping track of. The instance has already cleaned up after itself, so it's not necessary that the garbage collector call the finalizer.

It's important to know the difference between implementing IDisposable and a finalizer. Both clean up your object, but a finalizer is called by the garbage collector, and the Dispose method can be called from code.

Weak references

A WeakReference doesn't hold a real reference to an item on the heap, so that it can't be garbage collected. But when garbage collection hasn't occurred yet, you can still access the item through the WeakReference.

LISTING 2-85 Using WeakReference

static WeakReference data; 
public static void Run() 
{ 
    object result = GetData(); 
    // GC.Collect(); Uncommenting this line will make data.Target null 
    result = GetData(); 
}
private static object GetData() 
{ 
    if (data == null) 
    { 
        data = new WeakReference(LoadLargeList()); 
    } 
 
    if (data.Target == null) 
    { 
        data.Target = LoadLargeList(); 
    } 
    return data.Target; 
}

Using WeakReference is not a complete solution for a caching scenario. If you want to implement a cache, you should define an algorithm that decides which items should be removed from the cache. Upon removing, you turn a reference into a WeakReference and leave it up to the garbage collector.

Objective summary

  • Memory in C# consists of both the stack and the heap.
  • The heap is managed by the garbage collector.
  • The garbage collector frees any memory that is not referenced any more.
  • A finalizer is a special piece of code that's run by the garbage collector when it removes an object.
  • IDisposable can be implemented to free any unmanaged resources in a deterministic way.
  • Objects implementing IDisposable can be used with a using statement to make sure they are always freed.
  • A WeakReference can be used to maintain a reference to items that can be garbage collected when necessary.

Objective 2.7: Manipulate strings

Using strings in the .NET Framework

A string in C# is an object of type String whose value is text. The string keyword is just an alias for the .NET Framework's String.

String is a reference type that looks like value type (for example, the equality operators == and != are overloaded to compare on value, not on reference).

String is immutable, so it cannot be changed after it has been created. Every change to a string will create a new string.

Manipulating strings

StringBuilder

LISTING 2-87 Changing a character with a StringBuilder

System.Text.StringBuilder sb = new System.Text.StringBuilder("A initial value"); 
sb[0] = 'B';

LISTING 2-88 Using a StringBuilder in a loop

StringBuilder sb = new StringBuilder(string.Empty); 
 
for (int i = 0; i < 10000; i++) 
{ 
    sb.Append("x"); 
}

Although the StringBuilder class generally offers better performance than the String class, you should not automatically replace String with StringBuilder whenever you want to manipulate strings. Performance depends on the size of the string, the amount of memory to be allocated for the new string, the system on which your app is executing, and the type of operation. You should be prepared to test your app to determine whether StringBuilder actually offers a significant performance improvement.

StringWriter and StringReader

Internally, StringWriter and StringReader use a StringBuilder.

LISTING 2-89 Using a StringWriter as the output for an XmlWriter

var stringWriter = new StringWriter(); 
using (XmlWriter writer = XmlWriter.Create(stringWriter)) 
{ 
    writer.WriteStartElement("book"); 
    writer.WriteElementString("price", "19.95"); 
    writer.WriteEndElement(); 
    writer.Flush(); 
} 
string xml = stringWriter.ToString();
The value of xml is now: 
<?xml version=\"1.0\" encoding=\"utf-16\"?> 
    <book> 
        <price>19.95</price> 
    </book>

LISTING 2-90 Using a StringReader as the input for an XmlReader

var stringReader = new StringReader(xml); 
using (XmlReader reader = XmlReader.Create(stringReader)) 
{ 
    reader.ReadToFollowing("price"); 
    decimal price = decimal.Parse(reader.ReadInnerXml(),  
        new CultureInfo("en-US")); // Make sure that you read the decimal part correctly 
}

Searching for strings

Best Practices for Using Strings in .NET

LISTING 2-91 Using IndexOf and LastIndexOf

string value = "My Sample Value"; 
int indexOfp = value.IndexOf('p'); // returns 6 
int lastIndexOfm = value.LastIndexOf('m'); // returns 5

LISTING 2-92 Using StartsWith and EndsWith

string value = "<mycustominput>"; 
if (value.StartsWith("<")) { } 
if (value.EndsWith(">")) { }

LISTING 2-94 Changing a string with a regular expression

string pattern = "(Mr\\.? |Mrs\\.? |Miss |Ms\\.? )"; 
string[] names = { "Mr. Henry Hunt", "Ms. Sara Samuels",  
             "Abraham Adams", "Ms. Nicole Norris" }; 
 
foreach (string name in names) 
    Console.WriteLine(Regex.Replace(name, pattern, String.Empty));

Enumerating strings

A string implements IEnumerable and IEnumerable<Char>.

Formatting strings

LISTING 2-96 Overriding ToString

class Person 
{
    public Person(string firstName, string lastName) 
    {  
        this.FirstName = firstName; 
        this.LastName = lastName; 
    } 
 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
 
    public override string ToString() 
    { 
        return FirstName + LastName; 
    } 
} 
 
Person p = new Person("John", "Doe"); 
Console.WriteLine(p); // Displays 'John Doe'

LISTING 2-97 Displaying a number with a currency format string

double cost = 1234.56; 
Console.WriteLine(cost.ToString("C", 
                  new System.Globalization.CultureInfo("en-US"))); 
// Displays $1,234.56

LISTING 2-98 Displaying a DateTime with different format strings

DateTime d = new DateTime(2013, 4, 22); 
CultureInfo provider = new CultureInfo("en-US"); 
Console.WriteLine(d.ToString("d", provider)); // Displays 4/22/2013 
Console.WriteLine(d.ToString("D", provider)); // Displays Monday, April 22, 2013 
Console.WriteLine(d.ToString("M", provider)); // Displays April 22

LISTING 2-99 Implementing custom formatting on a type

class Person 
{ 
 …  
    public string ToString(string format) 
    { 
        if (string.IsNullOrWhiteSpace(format) || format = "G") format = "FL"; 
 
        format = format.Trim().ToUpperInvariant(); 
 
        switch (format) 
        {  
            case "FL": 
                return FirstName + " " + LastName; 
            case "LF": 
                return LastName + " " + FirstName; 
            case "FSL": 
                return FirstName + ", " + LastName; 
            case "LSF": 
                return LastName + ", " + FirstName; 
            default: 
                throw new FormatException(String.Format( 
                        "The '{0}' format string is not supported.", format)); 
        } 
    } 
}

IFormatProvider and IFormattable

All CultureInfo objects implement IFormatProvider. The CultureInfo object returns a culture-specific NumberFormatInfo or DateTimeFormatInfo if a string or DateTime is formatted.

Using IFormattable makes sure that you can integrate with the .NET Framework when it comes to formatting strings. When implementing IFormattable, you have support for string conversion by the Convert class (which has an overload that accepts an object and IFormatProvider).

LISTING 2-100 Creating a composite string formatting

int a = 1; 
int b = 2; 
string result = string.Format("a: {0}, b: {1}", a, b); 
Console.WriteLine(result); // Displays 'a: 1, b: 2'

Objective summary

  • A string is an immutable reference type.
  • When doing a lot of string manipulations, you should use a StringBuilder.
  • The String class offers a lot of methods for dealing with strings like IndexOf, LastIndexOf, StartsWith, EndsWith, and Substring.
  • Strings can be enumerated as a collection of characters.
  • Formatting is the process of displaying an object as a string.
  • You can use format strings to change how an object is converted to a string.
  • You can implement formatting for your own types.

Chapter summary

  • C# uses types such as class, struct, interface, and enum. Types have can have members such as methods, events, fields, properties, indexed properties, and constructors.
  • When working with types, you sometimes need to convert between them. This can be done either implicitly or explicitly. When creating your own types, you can add support for these types of conversions.
  • You use accessors such as public, private, protected, internal, and protected internal to enforce encapsulation and accessibility. Properties can be used to encapsulate data.
  • An object hierarchy can be created by inheritance, and you can have both interface and class inheritance. By marking members as virtual, a derived class can override the member.
  • Reflection is the process of inspecting metadata at runtime. Attributes can be used to add metadata to a type.
  • C# is a managed language, which means that a garbage collector makes sure that all managed objects are freed from memory whenever they are no longer in use.
  • Strings can be used for text. The string type is immutable. The .NET Framework offers a StringBuilder for manipulating large amounts of text. Strings can be searched, enumerated, and formatted for display.

Note Links

相关文章

网友评论

      本文标题:70-483.C2O5-7.Create and use typ

      本文链接:https://www.haomeiwen.com/subject/gruujftx.html