{"id":332,"date":"2023-10-20T13:34:21","date_gmt":"2023-10-20T05:34:21","guid":{"rendered":"https:\/\/miie.net\/?p=332"},"modified":"2023-10-20T13:34:21","modified_gmt":"2023-10-20T05:34:21","slug":"pro-c10-chapter-17-type-reflection-late-binding-attribute-and-dynamic-types","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=332","title":{"rendered":"Pro C#10 CHAPTER 17 Type Reflection, Late Binding, Attribute, and Dynamic Types"},"content":{"rendered":"<p>CHAPTER 17<\/p>\n<p>Type Reflection, Late Binding, Attribute, and Dynamic Types<\/p>\n<p>As shown in Chapter 16, assemblies are the basic unit of deployment in the .NET universe. Using the integrated Object Browser of Visual Studio (and numerous other IDEs), you can examine the types within a project\u2019s referenced set of assemblies. Furthermore, external tools such as ildasm.exe allow you to peek into the underlying CIL code, type metadata, and assembly manifest for a given .NET binary. In addition to this design-time investigation of .NET assemblies, you are also able to programmatically obtain this same information using the System.Reflection namespace. To this end, the first task of this chapter is to define the role of reflection and the necessity of .NET metadata.<br \/>\nThe next sections of the chapter examine several closely related topics, which hinge upon reflection services. For example, you will learn how a .NET client may employ dynamic loading and late binding to activate types it has no compile-time knowledge of. You will also learn how to insert custom metadata into your .NET assemblies using system-supplied and custom attributes. To put all of these (seemingly esoteric) topics into perspective, the chapter closes by demonstrating how to build several \u201csnap-in objects\u201d that you can plug into an extendable console application.<br \/>\nIn this chapter, you will also be introduced to the C# dynamic keyword and understand how loosely typed calls are mapped to the correct in-memory object using the Dynamic Language Runtime (DLR). After you understand the services provided by the DLR, you will see examples of using dynamic types to streamline how you can perform late-bound method calls (via reflection services) and to easily communicate with legacy COM libraries.<\/p>\n<p>\u25a0 Note  Don\u2019t confuse the C# dynamic keyword with the concept of a dynamic assembly (see Chapter 18). While you could use the dynamic keyword when building a dynamic assembly, these are ultimately two independent concepts.<\/p>\n<p>The Necessity of Type Metadata<br \/>\nThe ability to fully describe types (classes, interfaces, structures, enumerations, and delegates) using metadata is a key element of the .NET platform. Many .NET technologies, such as object serialization, require the ability to discover the format of types at runtime. Furthermore, cross-language interoperability, numerous compiler services, and an IDE\u2019s IntelliSense capabilities all rely on a concrete description of type.<\/p>\n<p>\u00a9 Andrew Troelsen, Phil Japikse 2022<br \/>\nA. Troelsen and P. Japikse, Pro C# 10 with .NET 6, <a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_17\"><a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_17\"><a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_17\">https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_17<\/a><\/a><\/a><\/p>\n<p>661<\/p>\n<p>Recall that the ildasm.exe utility allows you to view an assembly\u2019s type metadata. In the generated CarLibrary.il file (from Chapter 16), navigate to the METAINFO section to see all the CarLibrary\u2019s metadata. A small snippet is included here:<\/p>\n<p>\/\/ ==== M E T A I N F O ===<\/p>\n<p>\/\/ ===========================================================<br \/>\n\/\/ ScopeName : CarLibrary.dll<br \/>\n\/\/ MVID : {DF92DBD2-2C47-4CF8-B25E-7319F1351625}<br \/>\n\/\/ ===========================================================<br \/>\n\/\/ Global functions<br \/>\n\/\/<br \/>\n\/\/<br \/>\n\/\/ Global fields<br \/>\n\/\/<br \/>\n\/\/<br \/>\n\/\/ Global MemberRefs<br \/>\n\/\/<br \/>\n\/\/<br \/>\n\/\/ TypeDef #1<br \/>\n\/\/<br \/>\n\/\/  TypDefName: CarLibrary.Car<br \/>\n\/\/  Flags   : [Public] [AutoLayout] [Class] [Abstract] [AnsiClass] [BeforeFieldInit]<br \/>\n\/\/  Extends : [TypeRef] System.Object<br \/>\n\/\/  Field #1<br \/>\n\/\/<br \/>\n\/\/  Field Name: <PetName>k BackingField<br \/>\n\/\/  Flags   : [Private]<br \/>\n\/\/  CallCnvntn : [FIELD]<br \/>\n\/\/  Field type : String<br \/>\n...<\/p>\n<p>As you can see, the .NET type metadata is verbose (the actual binary format is much more compact). In fact, if I were to list the entire metadata description representing the CarLibrary.dll assembly, it would span several pages. Given that this act would be a woeful waste of paper, let\u2019s just glimpse into some key metadata descriptions of the CarLibrary.dll assembly.<\/p>\n<p>\u25a0 Note  Don\u2019t be too concerned with the exact syntax of every piece of .NET metadata in the next few sections. The bigger point to absorb is that .NET metadata is very descriptive and lists each internally defined (and externally referenced) type found within a given code base.<\/p>\n<p>Viewing (Partial) Metadata for the EngineStateEnum Enumeration<br \/>\nEach type defined within the current assembly is documented using a TypeDef #n token (where TypeDef is short for type definition). If the type being described uses a type defined within a separate .NET assembly, the referenced type is documented using a TypeRef #n token (where TypeRef is short for type reference).<br \/>\nA TypeRef token is a pointer (if you will) to the referenced type\u2019s full metadata definition in an external assembly. In a nutshell, .NET metadata is a set of tables that clearly mark all type definitions (TypeDefs) and referenced types (TypeRefs), all of which can be examined using ildasm.exe.<\/p>\n<p>As far as CarLibrary.dll goes, one TypeDef is the metadata description of the CarLibrary.<br \/>\nEngineStateEnum enumeration (your number may differ; TypeDef numbering is based on the order in which the C# compiler processes the file).<\/p>\n<p>\/\/ TypeDef #2<br \/>\n\/\/<br \/>\n\/\/  TypDefName: CarLibrary.EngineStateEnum<br \/>\n\/\/  Flags   : [Public] [AutoLayout] [Class] [Sealed] [AnsiClass]<br \/>\n\/\/  Extends : [TypeRef] System.Enum<br \/>\n\/\/  Field #1<br \/>\n\/\/<br \/>\n\/\/  Field Name: value<br \/>\n\/\/  Flags   : [Public] [SpecialName] [RTSpecialName]<br \/>\n\/\/  CallCnvntn: [FIELD]<br \/>\n\/\/  Field type: I4<br \/>\n\/\/<br \/>\n\/\/  Field #2<br \/>\n\/\/<br \/>\n\/\/  Field Name: EngineAlive<br \/>\n\/\/  Flags   : [Public] [Static] [Literal] [HasDefault]<br \/>\n\/\/  DefltValue: (I4) 0<br \/>\n\/\/  CallCnvntn: [FIELD]<br \/>\n\/\/  Field type: ValueClass CarLibrary.EngineStateEnum<br \/>\n\/\/<br \/>\n\/\/  Field #3 (04000007)<br \/>\n\/\/<br \/>\n\/\/  Field Name: EngineDead (04000007)<br \/>\n\/\/  Flags   : [Public] [Static] [Literal] [HasDefault] (00008056)<br \/>\n\/\/  DefltValue: (I4) 1<br \/>\n\/\/  CallCnvntn: [FIELD]<br \/>\n\/\/  Field type: ValueClass CarLibrary.EngineStateEnum<br \/>\n...<\/p>\n<p>Here, the TypDefName token is used to establish the name of the given type, which in this case is the custom CarLibrary.EngineStateEnum enum. The Extends metadata token is used to document the base type of a given .NET type (in this case, the referenced type, System.Enum). Each field of an enumeration is marked using the Field #n token.<\/p>\n<p>\u25a0 Note  While they look like typos, TypDefName does not have the e and DefltValue does not have the au<br \/>\none would expect.<\/p>\n<p>Viewing (Partial) Metadata for the Car Type<br \/>\nHere is a partial dump of the Car class that illustrates the following:<br \/>\n\u2022   How fields are defined in terms of .NET metadata<br \/>\n\u2022   How methods are documented via .NET metadata<br \/>\n\u2022   How an automatic property is represented in .NET metadata<\/p>\n<p>\/\/ TypeDef #1<br \/>\n\/\/<br \/>\n\/\/  TypDefName: CarLibrary.Car<br \/>\n\/\/  Flags   : [Public] [AutoLayout] [Class] [Abstract] [AnsiClass] [BeforeFieldInit]<br \/>\n\/\/  Extends : [TypeRef] System.Object<br \/>\n\/\/  Field #1<br \/>\n\/\/<br \/>\n\/\/  Field Name: <PetName>k BackingField<br \/>\n\/\/  Flags   : [Private]<br \/>\n\/\/  CallCnvntn: [FIELD]<br \/>\n\/\/  Field type: String<br \/>\n...<br \/>\n\/\/ Method #1<br \/>\n\/\/<br \/>\n\/\/  MethodName: get_PetName<br \/>\n\/\/  Flags   : [Public] [HideBySig] [ReuseSlot] [SpecialName]<br \/>\n\/\/  RVA : 0x00002050<br \/>\n\/\/  ImplFlags : [IL] [Managed]<br \/>\n\/\/  CallCnvntn: [DEFAULT]<br \/>\n\/\/  hasThis<br \/>\n\/\/  ReturnType: String<br \/>\n\/\/  No arguments.<br \/>\n...<br \/>\n\/\/  Method #2<br \/>\n\/\/<br \/>\n\/\/  MethodName: set_PetName<br \/>\n\/\/  Flags   : [Public] [HideBySig] [ReuseSlot] [SpecialName]<br \/>\n\/\/  RVA : 0x00002058<br \/>\n\/\/  ImplFlags : [IL] [Managed]<br \/>\n\/\/  CallCnvntn: [DEFAULT]<br \/>\n\/\/  hasThis<br \/>\n\/\/  ReturnType: Void<br \/>\n\/\/  1 Arguments<br \/>\n\/\/  Argument #1: String<br \/>\n\/\/  1 Parameters<br \/>\n\/\/  (1) ParamToken : Name : value flags: [none]<br \/>\n...<\/p>\n<p>\/\/  Property #1<br \/>\n\/\/<br \/>\n\/\/  Prop.Name : PetName<br \/>\n\/\/  Flags   : [none]<br \/>\n\/\/  CallCnvntn: [PROPERTY]<br \/>\n\/\/  hasThis<br \/>\n\/\/  ReturnType: String<br \/>\n\/\/  No arguments.<br \/>\n\/\/  DefltValue:<br \/>\n\/\/  Setter  : set_PetName<br \/>\n\/\/  Getter  : get_PetName<br \/>\n\/\/  0 Others<br \/>\n...<\/p>\n<p>First, note that the Car class metadata marks the type\u2019s base class (System.Object) and includes various flags that describe how this type was constructed (e.g., [Public], [Abstract], and whatnot). Methods (such as the Car\u2019s constructor) are described by their parameters, return value, and name.<br \/>\nNote how an automatic property results in a compiler-generated private backing field (which was named <PetName>k BackingField) and two compiler-generated methods (in the case of a read-write property) named, in this example, get_PetName() and set_PetName(). Finally, the actual property is mapped to the internal get\/set methods using the .NET metadata Getter\/Setter tokens.<\/p>\n<p>Examining a TypeRef<br \/>\nRecall that an assembly\u2019s metadata will describe not only the set of internal types (Car, EngineStateEnum, etc.) but also any external types the internal types reference. For example, given that CarLibrary.dll has defined two enumerations, you find a TypeRef block for the System.Enum type, as follows:<\/p>\n<p>\/\/ TypeRef #19<br \/>\n\/\/<br \/>\n\/\/ Token:   0x01000013<br \/>\n\/\/ ResolutionScope: 0x23000001<br \/>\n\/\/ TypeRefName: System.Enum<\/p>\n<p>Documenting the Defining Assembly<br \/>\nThe CarLibrary.il file also allows you to view the .NET metadata that describes the assembly itself using the Assembly token. The following is a partial dump of the manifest of CarLibrary.dll. Note that the version matches the assembly version set for the library.<\/p>\n<p>\/\/ Assembly<br \/>\n\/\/<br \/>\n\/\/  Token: 0x20000001<br \/>\n\/\/  Name : CarLibrary<br \/>\n\/\/  Public Key  :<br \/>\n\/\/  Hash Algorithm : 0x00008004<br \/>\n\/\/  Version: 1.0.0.1<br \/>\n\/\/  Major Version: 0x00000001<br \/>\n\/\/  Minor Version: 0x00000000<br \/>\n\/\/  Build Number: 0x00000000<br \/>\n\/\/  Revision Number: 0x00000001<br \/>\n\/\/  Locale: <null><br \/>\n\/\/  Flags : [none] (00000000)<\/p>\n<p>Documenting Referenced Assemblies<br \/>\nIn addition to the Assembly token and the set of TypeDef and TypeRef blocks, .NET metadata also makes use of AssemblyRef #n tokens to document each external assembly. Given that each .NET assembly references the System.Runtime base class library assembly, you will find an AssemblyRef for the System.Runtime assembly, as shown in the following code:<\/p>\n<p>\/\/ AssemblyRef #1<br \/>\n\/\/<br \/>\n\/\/  Token: 0x23000001<br \/>\n\/\/  Public Key or Token: b0 3f 5f 7f 11 d5 0a 3a<br \/>\n\/\/  Name: System.Runtime<br \/>\n\/\/  Version: 6.0.0.0<br \/>\n\/\/  Major Version: 0x00000006<br \/>\n\/\/  Minor Version: 0x00000000<br \/>\n\/\/  Build Number: 0x00000000<br \/>\n\/\/  Revision Number: 0x00000000<br \/>\n\/\/  Locale: <null><br \/>\n\/\/  HashValue Blob:<br \/>\n\/\/  Flags: [none] (00000000)<\/p>\n<p>Documenting String Literals<br \/>\nThe final point of interest regarding .NET metadata is the fact that every string literal in your code base is documented under the User Strings token.<\/p>\n<p>\/\/ User Strings<br \/>\n\/\/<br \/>\n\/\/ 70000001 : (23) L&quot;CarLibrary Version 2.0!&quot;<br \/>\n\/\/ 70000031 : (13) L&quot;Quiet time...&quot;<br \/>\n\/\/ 7000004d : (11) L&quot;Jamming {0}&quot;<br \/>\n\/\/ 70000065 : (32) L&quot;Eek! Your engine block exploded!&quot;<br \/>\n\/\/ 700000a7 : (34) L&quot;Ramming speed! Faster is better...&quot;<\/p>\n<p>\u25a0 Note as illustrated in the previous metadata listing, always be aware that all strings are clearly documented in the assembly metadata. This could have huge security consequences if you were to use string literals to represent passwords, credit card numbers, or other sensitive information.<\/p>\n<p>The next question on your mind may be (in the best-case scenario) \u201cHow can I leverage this information in my applications?\u201d or (in the worst-case scenario) \u201cWhy should I care about metadata?\u201d To address both points of view, allow me to introduce .NET reflection services. Be aware that the usefulness of the topics presented over the pages that follow may be a bit of a head-scratcher until this chapter\u2019s endgame. So, hang tight.<\/p>\n<p>\u25a0 Note  you will also find a number of CustomAttribute tokens displayed by the METAINFO section, which documents the attributes applied within the code base. you will learn about the role of .NET attributes later in this chapter.<\/p>\n<p>Understanding Reflection<br \/>\nIn the .NET universe, reflection is the process of runtime type discovery. Using reflection services, you can programmatically obtain the same metadata information generated by ildasm.exe using a friendly object model. For example, through reflection, you can obtain a list of all types contained within a given <em>.dll or<br \/>\n<\/em>.exe assembly, including the methods, fields, properties, and events defined by a given type. You can also dynamically discover the set of interfaces supported by a given type, the parameters of a method, and other related details (base classes, namespace information, manifest data, etc.).<br \/>\nLike any namespace, System.Reflection (which is defined in System.Runtime.dll) contains several related types. Table 17-1 lists some of the core items you should be familiar with.<\/p>\n<p>Table 17-1. A Sampling of Members of the System.Reflection Namespace<\/p>\n<p>Type    Meaning in Life<br \/>\nAssembly    This abstract class contains members that allow you to load, investigate, and manipulate an assembly.<br \/>\nAssemblyName    This class allows you to discover numerous details behind an assembly\u2019s identity (version information, culture information, etc.).<br \/>\nEventInfo   This abstract class holds information for a given event.<br \/>\nFieldInfo   This abstract class holds information for a given field.<br \/>\nMemberInfo  This is the abstract base class that defines common behaviors for the EventInfo, FieldInfo, MethodInfo, and PropertyInfo types.<br \/>\nMethodInfo  This abstract class contains information for a given method.<br \/>\nModule  This abstract class allows you to access a given module within a multifile assembly.<br \/>\nParameterInfo   This class holds information for a given parameter.<br \/>\nPropertyInfo    This abstract class holds information for a given property.<\/p>\n<p>To understand how to leverage the System.Reflection namespace to programmatically read .NET metadata, you need to first come to terms with the System.Type class.<\/p>\n<p>The System.Type Class<br \/>\nThe System.Type class defines members that can be used to examine a type\u2019s metadata, a great number of which return types from the System.Reflection namespace. For example, Type.GetMethods() returns an array of MethodInfo objects, Type.GetFields() returns an array of FieldInfo objects, and so on. The complete set of members exposed by System.Type is quite expansive; however, Table 17-2 offers a partial snapshot of the members supported by System.Type (see the .NET documentation for full details).<\/p>\n<p>Table 17-2. Select Members of System.Type<\/p>\n<p>Member  Meaning in Life<br \/>\nIsAbstract IsArray IsClass IsCOMObject IsEnum<br \/>\nIsGenericTypeDefinition IsGenericParameter IsInterface<br \/>\nIsPrimitive IsNestedPrivate IsNestedPublic IsSealed IsValueType These properties (among others) allow you to discover a number of basic traits about the Type you are referring to (e.g., if it is an abstract entity, an array, a nested class, etc.).<br \/>\nGetConstructors() GetEvents() GetFields() GetInterfaces() GetMembers() GetMethods() GetNestedTypes() GetProperties()    These methods (among others) allow you to obtain an array representing the items (interface, method, property, etc.) you are interested in. Each method returns a related array (e.g., GetFields() returns a FieldInfo array, GetMethods() returns a MethodInfo array, etc.). Be aware that each of these methods has a singular form (e.g., GetMethod(), GetProperty(), etc.) that allows you to retrieve a specific item by name, rather than an array of all related items.<br \/>\nFindMembers()   This method returns a MemberInfo array based on search criteria.<br \/>\nGetType()   This static method returns a Type instance given a string name.<br \/>\nInvokeMember()  This method allows \u201clate binding\u201d for a given item. You\u2019ll learn about late binding later in this chapter.<\/p>\n<p>Obtaining a Type Reference Using System.Object.GetType()<br \/>\nYou can obtain an instance of the Type class in a variety of ways. However, the one thing you cannot do is directly create a Type object using the new keyword, as Type is an abstract class. Recall that System.<br \/>\nObject defines a method named GetType(), which returns an instance of the Type class that represents the metadata for the current object.<\/p>\n<p>\/\/ Obtain type information using a SportsCar instance. SportsCar sc = new SportsCar();<br \/>\nType t = sc.GetType();<\/p>\n<p>Obviously, this approach will work only if you have compile-time knowledge of the type you want to reflect over (SportsCar in this case) and currently have an instance of the type in memory. Given this<br \/>\nrestriction, it should make sense that tools such as ildasm.exe do not obtain type information by directly calling System.Object.GetType() for each type since that ildasm.exe was not compiled against your custom assemblies.<\/p>\n<p>Obtaining a Type Reference Using typeof()<br \/>\nThe next way to obtain type information is using the C# typeof operator, like so:<\/p>\n<p>\/\/ Get the type using typeof. Type t = typeof(SportsCar);<\/p>\n<p>Unlike System.Object.GetType(), the typeof operator is helpful, in that you do not need to first create an object instance to extract type information. However, your code base must still have compile- time knowledge of the type you are interested in examining, as typeof expects the strongly typed name of the type.<\/p>\n<p>Obtaining a Type Reference Using System.Type.GetType()<br \/>\nTo obtain type information in a more flexible manner, you may call the static GetType() member of the System.Type class and specify the fully qualified string name of the type you are interested in examining. Using this approach, you do not need to have compile-time knowledge of the type you are extracting metadata from, given that Type.GetType() takes an instance of the omnipresent System.String.<\/p>\n<p>\u25a0 Note  When i say you do not need compile-time knowledge when calling Type.GetType(), i am referring to the fact that this method can take any string value whatsoever (rather than a strongly typed variable). of course, you would still need to know the name of the type in a \u201cstringified\u201d format!<\/p>\n<p>The Type.GetType() method has been overloaded to allow you to specify two Boolean parameters, one of which controls whether an exception should be thrown if the type cannot be found, and the other of which establishes the case sensitivity of the string. To illustrate, ponder the following:<\/p>\n<p>\/\/ Obtain type information using the static Type.GetType() method<br \/>\n\/\/ (don't throw an exception if SportsCar cannot be found and ignore case).<br \/>\nType t = Type.GetType(&quot;CarLibrary.SportsCar&quot;, false, true);<\/p>\n<p>In the previous example, notice that the string you are passing into GetType() makes no mention of the assembly containing the type. In this case, the assumption is that the type is defined within the currently executing assembly. However, when you want to obtain metadata for a type within an external assembly, the string parameter is formatted using the type\u2019s fully qualified name, followed by a comma, followed by the friendly name (the assembly name without any version information) of the assembly containing the type, like so:<\/p>\n<p>\/\/ Obtain type information for a type within an external assembly. Type t = Type.GetType(&quot;CarLibrary.SportsCar, CarLibrary&quot;);<\/p>\n<p>As well, do know that the string passed into Type.GetType() may specify a plus token (+) to denote a nested type. Assume you want to obtain type information for an enumeration (SpyOptions) nested within a class named JamesBondCar. To do so, you would write the following:<\/p>\n<p>\/\/ Obtain type information for a nested enumeration<br \/>\n\/\/ within the current assembly.<br \/>\nType t = Type.GetType(&quot;CarLibrary.JamesBondCar+SpyOptions&quot;);<\/p>\n<p>Building a Custom Metadata Viewer<br \/>\nTo illustrate the basic process of reflection (and the usefulness of System.Type), let\u2019s create a Console Application project named MyTypeViewer. This program will display details of the methods, properties, fields, and supported interfaces (in addition to some other points of interest) for any type within System. Runtime.dll (recall all .NET applications have automatic access to this framework class library) or a type within MyTypeViewer itself. Once the application has been created, be sure to import the System. Reflection namespace.<\/p>\n<p>\/\/ Need to import this namespace to do any reflection! using System.Reflection;<\/p>\n<p>Reflecting on Methods<br \/>\nSeveral static methods will be added to the Program.cs file, each of which takes a single System.Type parameter and returns void. First you have ListMethods(), which (as you might guess) prints the name of each method defined by the incoming type. Notice how Type.GetMethods() returns an array of System. Reflection.MethodInfo objects, which can be enumerated with a standard foreach loop, as follows:<\/p>\n<p>\/\/ Display method names of type. static void ListMethods(Type t)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Methods <\/em><\/strong><\/strong>&quot;); MethodInfo[] mi = t.GetMethods(); foreach(MethodInfo m in mi)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;-&gt;{0}&quot;, m.Name);<br \/>\n}<br \/>\nConsole.WriteLine();<br \/>\n}<\/p>\n<p>Here, you are simply printing the name of the method using the MethodInfo.Name property. As you might guess, MethodInfo has many additional members that allow you to determine whether the method is static, virtual, generic, or abstract. As well, the MethodInfo type allows you to obtain the method\u2019s return value and parameter set. You\u2019ll spruce up the implementation of ListMethods() in just a bit.<br \/>\nIf you wanted, you could also build a fitting LINQ query to enumerate the names of each method.<br \/>\nRecall from Chapter 13, LINQ to Objects allows you to build strongly typed queries that can be applied to in-memory object collections. As a good rule of thumb, whenever you find blocks of looping or decision programming logic, you could make use of a related LINQ query. For example, you could rewrite the previous method with LINQ like this:<\/p>\n<p>static void ListMethods(Type t)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Methods <\/em><\/strong><\/strong>&quot;);<br \/>\nvar methodNames = from n in t.GetMethods() orderby n.Name select n.Name;<br \/>\n\/\/ Using LINQ extensions:<br \/>\n\/\/ var methodNames = t.GetMethods().OrderBy(m=&gt;m.Name).Select(m=&gt;m.Name); foreach (var name in methodNames)<br \/>\n{<\/p>\n<p>Console.WriteLine(&quot;-&gt;{0}&quot;, name);<br \/>\n}<br \/>\nConsole.WriteLine();<br \/>\n}<\/p>\n<p>Reflecting on Fields and Properties<br \/>\nThe implementation of ListFields() is similar. The only notable difference is the call to Type.GetFields() and the resulting FieldInfo array. Again, to keep things simple, you are printing out only the name of each field using a LINQ query.<\/p>\n<p>\/\/ Display field names of type. static void ListFields(Type t)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Fields <\/em><\/strong><\/strong>&quot;);<br \/>\n\/\/ var fieldNames = from f in t.GetFields() orderby f.Name select f.Name; var fieldNames = t.GetFields().OrderBy(m=&gt;m.Name).Select(x=&gt;x.Name);<br \/>\nforeach (var name in fieldNames)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;-&gt;{0}&quot;, name);<br \/>\n}<br \/>\nConsole.WriteLine();<br \/>\n}<\/p>\n<p>The logic to display a type\u2019s properties is similar.<\/p>\n<p>\/\/ Display property names of type. static void ListProps(Type t)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Properties <\/em><\/strong><\/strong>&quot;);<br \/>\nvar propNames = from p in t.GetProperties() orderby p.Name select p.Name;<br \/>\n\/\/var propNames = t.GetProperties().Select(p=&gt;p.Name); foreach (var name in propNames)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;-&gt;{0}&quot;, name);<br \/>\n}<br \/>\nConsole.WriteLine();<br \/>\n}<\/p>\n<p>Reflecting on Implemented Interfaces<br \/>\nNext, you will author a method named ListInterfaces() that will print the names of any interfaces supported on the incoming type. The only point of interest here is that the call to GetInterfaces() returns an array of System.Types! This should make sense given that interfaces are, indeed, types.<\/p>\n<p>\/\/ Display implemented interfaces. static void ListInterfaces(Type t)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Interfaces <\/em><\/strong><\/strong>&quot;);<\/p>\n<p>var ifaces = from i in t.GetInterfaces() orderby i.Name select i;<br \/>\n\/\/var ifaces = t.GetInterfaces().OrderBy(i=&gt;i.Name); foreach(Type i in ifaces)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;-&gt;{0}&quot;, i.Name);<br \/>\n}<br \/>\n}<\/p>\n<p>\u25a0 Note  Be aware that a majority of the \u201cget\u201d methods of System.Type (GetMethods(), GetInterfaces(), etc.) have been overloaded to allow you to specify values from the BindingFlags enumeration. This provides a greater level of control on exactly what should be searched for (e.g., only static members, only public members, include private members, etc.). Consult the documentation for details.<\/p>\n<p>Displaying Various Odds and Ends<br \/>\nLast but not least, you have one final helper method that will simply display various statistics (indicating whether the type is generic, what the base class is, whether the type is sealed, etc.) regarding the incoming type.<\/p>\n<p>\/\/ Just for good measure.<br \/>\nstatic void ListVariousStats(Type t)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Various Statistics <\/em><\/strong><\/strong>&quot;); Console.WriteLine(&quot;Base class is: {0}&quot;, t.BaseType); Console.WriteLine(&quot;Is type abstract? {0}&quot;, t.IsAbstract); Console.WriteLine(&quot;Is type sealed? {0}&quot;, t.IsSealed); Console.WriteLine(&quot;Is type generic? {0}&quot;, t.IsGenericTypeDefinition); Console.WriteLine(&quot;Is type a class type? {0}&quot;, t.IsClass); Console.WriteLine();<br \/>\n}<\/p>\n<p>Adding the Top-Level Statements<br \/>\nThe top-level statements of the Program.cs file prompts the user for the fully qualified name of a type. Once you obtain this string data, you pass it into the Type.GetType() method and send the extracted System.Type into each of your helper methods. This process repeats until the user presses Q to terminate the application.<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Welcome to MyTypeViewer <\/em><\/strong><\/strong>&quot;); string typeName = &quot;&quot;;<\/p>\n<p>do<br \/>\n{<br \/>\nConsole.WriteLine(&quot;\\nEnter a type name to evaluate&quot;); Console.Write(&quot;or enter Q to quit: &quot;);<\/p>\n<p>\/\/ Get name of type.<br \/>\ntypeName = Console.ReadLine();<\/p>\n<p>\/\/ Does user want to quit?<br \/>\nif (typeName.Equals(&quot;Q&quot;,StringComparison.OrdinalIgnoreCase))<br \/>\n{<br \/>\nbreak;<br \/>\n}<\/p>\n<p>\/\/ Try to display type. try<br \/>\n{<br \/>\nType t = Type.GetType(typeName); Console.WriteLine(&quot;&quot;); ListVariousStats(t); ListFields(t);<br \/>\nListProps(t);<br \/>\nListMethods(t);<br \/>\nListInterfaces(t);<br \/>\n}<br \/>\ncatch<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Sorry, can't find type&quot;);<br \/>\n}<br \/>\n} while (true);<\/p>\n<p>At this point, MyTypeViewer.exe is ready to take a test-drive. For example, run your application and enter the following fully qualified names (be aware that Type.GetType() requires case-sensitive string names):<br \/>\n\u2022System.Int32<br \/>\n\u2022System.Collections.ArrayList<br \/>\n\u2022System.Threading.Thread<br \/>\n\u2022System.Void<br \/>\n\u2022System.Math<br \/>\nFor example, here is some partial output when specifying System.Math:<\/p>\n<p><strong><strong><em> Welcome to MyTypeViewer <\/em><\/strong><\/strong> Enter a type name to evaluate<br \/>\nor enter Q to quit: System.Math<\/p>\n<p><strong><strong><em> Various Statistics <\/em><\/strong><\/strong> Base class is: System.Object Is type abstract? True<br \/>\nIs type sealed? True Is type generic? False<br \/>\nIs type a class type? True<\/p>\n<p><strong><strong><em> Fields <\/em><\/strong><\/strong><br \/>\n-&gt;E<br \/>\n-&gt;PI<br \/>\n-&gt;Tau<\/p>\n<p><strong><strong><em> Properties <\/em><\/strong><\/strong><\/p>\n<p><strong><strong><em> Methods <\/em><\/strong><\/strong><br \/>\n-&gt;Abs<br \/>\n-&gt;Abs<br \/>\n...<br \/>\n-&gt;Acos<br \/>\n-&gt;Asin<br \/>\n-&gt;Atan<br \/>\n-&gt;Atan2<br \/>\n-&gt;Ceiling<br \/>\n-&gt;Cos<br \/>\n...<\/p>\n<p>Notice the repeated listing for Abs. This is because there will be at least one overload for the Abs()<br \/>\nmethod. The code will be expanded to show the parameters and return types shortly.<\/p>\n<p>Reflecting on Static Types<br \/>\nIf you enter System.Console in the previous method, an exception will be thrown in the first helper method because the value for t will be null. Static types cannot be loaded using the Type.GetType(typeName) method. Instead, you must use another mechanism, the typeof function from System.Type. Update the program to handle the System.Console special case like this:<\/p>\n<p>Type t = Type.GetType(typeName);<br \/>\nif (t == null &amp;&amp; typeName.Equals(&quot;System.Console&quot;, StringComparison.OrdinalIgnoreCase))<br \/>\n{<br \/>\nt = typeof(System.Console);<br \/>\n}<\/p>\n<p>Reflecting on Generic Types<br \/>\nWhen you call Type.GetType() to obtain metadata descriptions of generic types, you must make use of a special syntax involving a \u201cbacktick\u201d character (`) followed by a numerical value that represents the number of type parameters the type supports. For example, if you want to print out the metadata description of System.Collections.Generic.List<T>, you need to pass the following string into your application:<\/p>\n<p>System.Collections.Generic.List`1<\/p>\n<p>Here, you are using the numerical value of 1, given that List<T> has only one type parameter. However, if you want to reflect over Dictionary&lt;TKey, TValue&gt;, supply the value 2, like so:<\/p>\n<p>System.Collections.Generic.Dictionary`2<\/p>\n<p>Reflecting on Method Parameters and Return Values<br \/>\nSo far, so good! Next, we will make a minor enhancement to the current application. Specifically, you will update the ListMethods() helper function to list not only the name of a given method but also the return type and incoming parameter types. The MethodInfo type provides the ReturnType property and GetParameters()<\/p>\n<p>method for these tasks. In the following modified code, notice that you are building a string that contains the type and name of each parameter using a nested foreach loop (without the use of LINQ):<\/p>\n<p>static void ListMethods(Type t)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Methods <\/em><\/strong><\/strong>&quot;);<br \/>\nMethodInfo[] mi = t.GetMethods().OrderBy(m=&gt;m.Name).ToArray(); foreach (MethodInfo m in mi)<br \/>\n{<br \/>\n\/\/ Get return type.<br \/>\nstring retVal = m.ReturnType.FullName; string paramInfo = &quot;( &quot;;<br \/>\n\/\/ Get params.<br \/>\nforeach (ParameterInfo pi in m.GetParameters())<br \/>\n{<br \/>\nparamInfo += string.Format(&quot;{0} {1} &quot;, pi.ParameterType, pi.Name);<br \/>\n}<br \/>\nparamInfo += &quot; )&quot;;<\/p>\n<p>\/\/ Now display the basic method sig.<br \/>\nConsole.WriteLine(&quot;-&gt;{0} {1} {2}&quot;, retVal, m.Name, paramInfo);<br \/>\n}<br \/>\nConsole.WriteLine();<br \/>\n}<\/p>\n<p>If you now run this updated application, you will find that the methods of a given type are much more detailed and the mystery of the repeated methods is solved. If you enter System.Math into the program, both of the Abs() methods (and all other methods) will display the return type and the parameter(s).<\/p>\n<p><strong><strong><em> Methods <\/em><\/strong><\/strong><br \/>\n-&gt;System.Double Abs ( System.Double value )<br \/>\n-&gt;System.Single Abs ( System.Single value )<\/p>\n<p>The current implementation of ListMethods() is helpful, in that you can directly investigate each parameter and method return type using the System.Reflection object model. As an extreme shortcut, be aware that all of the XXXInfo types (MethodInfo, PropertyInfo, EventInfo, etc.) have overridden ToString() to display the signature of the item requested. Thus, you could also implement ListMethods()<br \/>\nas follows (once again using LINQ, where you simply select all MethodInfo objects, rather than only the Name<br \/>\nvalues):<\/p>\n<p>static void ListMethods(Type t)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Methods <\/em><\/strong><\/strong>&quot;);<br \/>\nvar methods = t.GetMethods().OrderBy(m=&gt;m.Name); foreach (var m in methods)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;-&gt;{0}&quot;, m);<br \/>\n}<br \/>\nConsole.WriteLine();<br \/>\n}<\/p>\n<p>Interesting stuff, huh? Clearly, the System.Reflection namespace and System.Type class allow you to reflect over many other aspects of a type beyond what MyTypeViewer is currently displaying. As you would hope, you can obtain a type\u2019s events, get the list of any generic parameters for a given member, and glean dozens of other details.<br \/>\nNevertheless, at this point you have created a (somewhat capable) object browser. The major limitation with this specific example is that you have no way to reflect beyond the current assembly (MyTypeViewer)<br \/>\nor assemblies in the base class libraries that are always referenced. This begs the question \u201cHow can I build applications that can load (and reflect over) assemblies not referenced at compile time?\u201d Glad you asked.<\/p>\n<p>Dynamically Loading Assemblies<br \/>\nThere will be times when you need to load assemblies on the fly programmatically, even if there is no record of said assembly in the manifest. Formally speaking, the act of loading external assemblies on demand is known as a dynamic load.<br \/>\nSystem.Reflection defines a class named Assembly. Using this class, you can dynamically load an assembly, as well as discover properties about the assembly itself. In essence, the Assembly class provides methods that allow you to programmatically load assemblies off disk.<br \/>\nTo illustrate dynamic loading, create a new Console Application project named ExternalAssemblyReflector. Your task is to construct code that prompts for the name of an assembly (minus any extension) to load dynamically. You will pass the Assembly reference into a helper method named DisplayTypes(), which will simply print the names of each class, interface, structure, enumeration, and delegate it contains. The code is refreshingly simple.<\/p>\n<p>using System.Reflection;<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> External Assembly Viewer <\/em><\/strong><\/strong>&quot;); string asmName = &quot;&quot;;<br \/>\nAssembly asm = null; do<br \/>\n{<br \/>\nConsole.WriteLine(&quot;\\nEnter an assembly to evaluate&quot;); Console.Write(&quot;or enter Q to quit: &quot;);<br \/>\n\/\/ Get name of assembly. asmName = Console.ReadLine();<br \/>\n\/\/ Does user want to quit?<br \/>\nif (asmName.Equals(&quot;Q&quot;,StringComparison.OrdinalIgnoreCase))<br \/>\n{<br \/>\nbreak;<br \/>\n}<\/p>\n<p>\/\/ Try to load assembly. try<br \/>\n{<br \/>\nasm = Assembly.LoadFrom(asmName); DisplayTypesInAsm(asm);<br \/>\n}<br \/>\ncatch<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Sorry, can't find assembly.&quot;);<br \/>\n}<\/p>\n<p>} while (true);<\/p>\n<p>static void DisplayTypesInAsm(Assembly asm)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;\\n<strong><strong><em> Types in Assembly <\/em><\/strong><\/strong>&quot;); Console.WriteLine(&quot;-&gt;{0}&quot;, asm.FullName);<br \/>\nType[] types = asm.GetTypes(); foreach (Type t in types)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Type: {0}&quot;, t);<br \/>\n}<br \/>\nConsole.WriteLine(&quot;&quot;);<br \/>\n}<\/p>\n<p>If you want to reflect over CarLibrary.dll, you will need to copy the CarLibrary.dll binary (from the previous chapter) to the project directory (if using Visual Studio Code) or to the \\bin\\Debug\\net6.0 directory (if using Visual Studio) of the ExternalAssemblyReflector application to run this program. Enter CarLibrary (the extension is optional) when prompted, and the output will look like this:<\/p>\n<p><strong><strong><em> External Assembly Viewer <\/em><\/strong><\/strong> Enter an assembly to evaluate<br \/>\nor enter Q to quit: CarLibrary<\/p>\n<p><strong><strong><em> Types in Assembly <\/em><\/strong><\/strong><br \/>\n-&gt;CarLibrary, Version=1.0.0.1, Culture=neutral, PublicKeyToken=null Type: CarLibrary.Car<br \/>\nType: CarLibrary.EngineStateEnum Type: CarLibrary.MiniVan<br \/>\nType: CarLibrary.MusicMediaEnum Type: CarLibrary.MyInternalClass Type: CarLibrary.SportsCar<\/p>\n<p>The LoadFrom method can also take an absolute path to the assembly you want to view (e.g., C:\\MyApp\\ MyAsm.dll). With this method, you can pass in a full path to your Console Application project. Thus, if CarLibrary.dll was located under C:\\MyCode, you could enter C:\\MyCode\\CarLibrary (the extension is still optional).<\/p>\n<p>Reflecting on Framework Assemblies<br \/>\nThe Assembly.Load() method has several overloads. One variation allows you to specify a culture value (for localized assemblies), as well as a version number and public key token value (for framework assemblies).<br \/>\nCollectively speaking, the set of items identifying an assembly is termed the display name. The format of a display name is a comma-delimited string of name-value pairs that begins with the friendly name of the assembly, followed by optional qualifiers (that may appear in any order). Here is the template to follow (optional items appear in parentheses):<\/p>\n<p>Name (,Version = major.minor.build.revision) (,Culture = culture token) (,PublicKeyToken= public key token)<\/p>\n<p>When you are crafting a display name, the convention PublicKeyToken=null indicates that binding and matching against a nonstrongly named assembly is required. Additionally, Culture=&quot;&quot; indicates matching against the default culture of the target machine. Here is an example:<\/p>\n<p>\/\/ Load version 1.0.0.1 of CarLibrary using the default culture. Assembly a =<br \/>\nAssembly.Load(&quot;CarLibrary, Version=1.0.0.1, PublicKeyToken=null, Culture=\\&quot;\\&quot;&quot;);<br \/>\n\/\/ The quotes must be escaped with back slashes in C#<\/p>\n<p>Also be aware that the System.Reflection namespace supplies the AssemblyName type, which allows you to represent the preceding string information in a handy object variable. Typically, this class is used in conjunction with System.Version, which is a wrapper around an assembly\u2019s version number. Once you have established the display name, it can then be passed into the overloaded Assembly.Load() method, like so:<\/p>\n<p>\/\/ Make use of AssemblyName to define the display name. AssemblyName asmName;<br \/>\nasmName = new AssemblyName(); asmName.Name = &quot;CarLibrary&quot;; Version v = new Version(&quot;1.0.0.1&quot;); asmName.Version = v;<br \/>\nAssembly a = Assembly.Load(asmName);<\/p>\n<p>To load a .NET Framework assembly (not .NET), the Assembly.Load() parameter should specify a PublicKeyToken value. With .NET, it\u2019s not required, due to the diminished use of strong naming. For<br \/>\nexample, assume you have a new Console Application project named FrameworkAssemblyViewer that has a reference to the Microsoft.EntityFrameworkCore package. As a reminder, this can all be done with the<br \/>\n.NET command-line interface (CLI).<\/p>\n<p>dotnet new console -lang c# -n FrameworkAssemblyViewer -o .\\FrameworkAssemblyViewer -f net6.0 dotnet sln .\\Chapter17_AllProjects.sln add .\\FrameworkAssemblyViewer<br \/>\ndotnet add .\\FrameworkAssemblyViewer package Microsoft.EntityFrameworkCore -v 6.0.0<\/p>\n<p>Recall that when you reference another assembly, a copy of that assembly is copied into the output directory of the referencing project. Build the project using the CLI.<\/p>\n<p>dotnet build<\/p>\n<p>With the project created, EntityFrameworkCode referenced, and the project built, you can now load it and inspect it. Given that the number of types in this assembly is quite large, the following application prints out only the names of public enums, using a simple LINQ query:<\/p>\n<p>using System.Reflection;<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> The Framework Assembly Reflector App <\/em><\/strong><\/strong>\\n&quot;);<\/p>\n<p>\/\/ Load Microsoft.EntityFrameworkCore.dll var displayName =<br \/>\n&quot;Microsoft.EntityFrameworkCore, Version=6.0.0.0, Culture=neutral, PublicKeyToken=a db9793829ddae60&quot;;<br \/>\nAssembly asm = Assembly.Load(displayName); DisplayInfo(asm); Console.WriteLine(&quot;Done!&quot;);<\/p>\n<p>Console.ReadLine();<\/p>\n<p>static void DisplayInfo(Assembly a)<br \/>\n{<br \/>\nAssemblyName asmNameInfo = a.GetName(); Console.WriteLine(&quot;<strong><strong><em> Info about Assembly <\/em><\/strong><\/strong>&quot;); Console.WriteLine($&quot;Asm Name: {asmNameInfo.Name}&quot;); Console.WriteLine($&quot;Asm Version: {asmNameInfo.Version}&quot;);<br \/>\nConsole.WriteLine($&quot;Asm Culture: {asmNameInfo.CultureInfo.DisplayName}&quot;);<\/p>\n<p>Console.WriteLine(&quot;\\nHere are the public enums:&quot;);<br \/>\n\/\/ Use a LINQ query to find the public enums.<br \/>\nvar publicEnums = a.GetTypes().Where(p=&gt;p.IsEnum &amp;&amp; p.IsPublic); foreach (var pe in publicEnums)<br \/>\n{<br \/>\nConsole.WriteLine(pe);<br \/>\n}<br \/>\n}<\/p>\n<p>At this point, you should understand how to use some of the core members of the System.Reflection namespace to discover metadata at runtime. Of course, you likely will not need to build custom object browsers often (if ever). However, reflection services are the foundation for many common programming activities, including late binding.<\/p>\n<p>Understanding Late Binding<br \/>\nSimply put, late binding is a technique in which you can create an instance of a given type and invoke its members at runtime without having hard-coded compile-time knowledge of its existence. When you are building an application that binds late to a type in an external assembly, you have no reason to set a reference to the assembly; therefore, the caller\u2019s manifest has no direct listing of the assembly.<br \/>\nAt first glance, it is not easy to see the value of late binding. It is true that if you can \u201cbind early\u201d to an object (e.g., add an assembly reference and allocate the type using the C# new keyword), you should opt to do so. For one reason, early binding allows you to determine errors at compile time, rather than at runtime. Nevertheless, late binding does have a critical role in any extendable application you may be building. You will have a chance to build such an \u201cextendable\u201d program later in this chapter, in the section \u201cBuilding an Extendable Application.\u201d Until then, let\u2019s examine the role of the Activator class.<\/p>\n<p>The System.Activator Class<br \/>\nThe System.Activator class is the key to the .NET late-binding process. For the next example, you are interested only in the Activator.CreateInstance() method, which is used to create an instance of a type through late binding. This method has been overloaded numerous times to provide a good deal of flexibility. The simplest variation of the CreateInstance() member takes a valid Type object that describes the entity you want to allocate into memory on the fly.<br \/>\nCreate a new Console Application project named LateBindingApp and update the Program.cs file as follows:<\/p>\n<p>using System.Reflection;<\/p>\n<p>\/\/ This program will load an external library,<br \/>\n\/\/ and create an object using late binding.<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Fun with Late Binding <\/em><\/strong><\/strong>&quot;);<br \/>\n\/\/ Try to load a local copy of CarLibrary.<br \/>\nAssembly a = null; try<br \/>\n{<br \/>\na = Assembly.LoadFrom(&quot;CarLibrary&quot;);<br \/>\n}<br \/>\ncatch(FileNotFoundException ex)<br \/>\n{<br \/>\nConsole.WriteLine(ex.Message); return;<br \/>\n}<br \/>\nif(a != null)<br \/>\n{<br \/>\nCreateUsingLateBinding(a);<br \/>\n}<br \/>\nConsole.ReadLine();<\/p>\n<p>static void CreateUsingLateBinding(Assembly asm)<br \/>\n{<br \/>\ntry<br \/>\n{<br \/>\n\/\/ Get metadata for the Minivan type.<br \/>\nType miniVan = asm.GetType(&quot;CarLibrary.MiniVan&quot;);<\/p>\n<p>\/\/ Create a Minivan instance on the fly.<br \/>\nobject obj = Activator.CreateInstance(miniVan); Console.WriteLine(&quot;Created a {0} using late binding!&quot;, obj);<br \/>\n}<br \/>\ncatch(Exception ex)<br \/>\n{<br \/>\nConsole.WriteLine(ex.Message);<br \/>\n}<br \/>\n}<\/p>\n<p>Now, before you run this application, you will need to manually place a copy of CarLibrary.dll into the project file folder (or bin\\Debug\\net6.0 folder if you are using Visual Studio) of this new application.<\/p>\n<p>\u25a0 Note  Don\u2019t add a reference to CarLibrary.dll for this example! The whole point of late binding is that you are trying to create an object that is not known at compile time.<\/p>\n<p>Notice that the Activator.CreateInstance() method returns a System.Object rather than a strongly typed MiniVan. Therefore, if you apply the dot operator on the obj variable, you will fail to see any members of the MiniVan class. At first glance, you might assume you can remedy this problem with an explicit cast, like so:<\/p>\n<p>\/\/ Cast to get access to the members of MiniVan?<br \/>\n\/\/ Nope! Compiler error!<br \/>\nobject obj = (MiniVan)Activator.CreateInstance(minivan);<\/p>\n<p>However, because your program has not added a reference to CarLibrary.dll, you cannot use the C# using keyword to import the CarLibrary namespace, and therefore, you cannot use a MiniVan type during the casting operation! Remember that the whole point of late binding is to create instances of objects for which there is no compile-time knowledge. Given this, how can you invoke the underlying methods of the MiniVan object stored in the System.Object reference? The answer, of course, is by using reflection.<\/p>\n<p>Invoking Methods with No Parameters<br \/>\nAssume you want to invoke the TurboBoost() method of the MiniVan. As you recall, this method will set the state of the engine to \u201cdead\u201d and display an informational message. The first step is to obtain a MethodInfo object for the TurboBoost() method using Type.GetMethod(). From the resulting MethodInfo, you are then able to call MiniVan.TurboBoost using Invoke(). MethodInfo.Invoke() requires you to send in all parameters that are to be given to the method represented by MethodInfo. These parameters are represented by an array of System.Object types (as the parameters for a given method could be any number of various entities).<br \/>\nGiven that TurboBoost() does not require any parameters, you can simply pass null (meaning \u201cthis method has no parameters\u201d). Update your CreateUsingLateBinding() method as follows (updates in bold):<\/p>\n<p>static void CreateUsingLateBinding(Assembly asm)<br \/>\n{<br \/>\ntry<br \/>\n{<br \/>\n\/\/ Get metadata for the Minivan type.<br \/>\nType miniVan = asm.GetType(&quot;CarLibrary.MiniVan&quot;);<\/p>\n<p>\/\/ Create the Minivan on the fly.<br \/>\nobject obj = Activator.CreateInstance(miniVan); Console.WriteLine($&quot;Created a {obj} using late binding!&quot;);<\/p>\n<p>\/\/ Get info for TurboBoost.<br \/>\nMethodInfo mi = miniVan.GetMethod(&quot;TurboBoost&quot;);<\/p>\n<p>\/\/ Invoke method ('null' for no parameters). mi.Invoke(obj, null);<br \/>\n}<br \/>\ncatch(Exception ex)<br \/>\n{<br \/>\nConsole.WriteLine(ex.Message);<br \/>\n}<br \/>\n}<\/p>\n<p>At this point, you will see the message in the console that your engine exploded.<\/p>\n<p>Invoking Methods with Parameters<br \/>\nWhen you want to use late binding to invoke a method requiring parameters, you should package up the arguments as a loosely typed array of objects. The version of the Car class that has a radio and has the following method:<\/p>\n<p>public void TurnOnRadio(bool musicOn, MusicMediaEnum mm)<br \/>\n=&gt; MessageBox.Show(musicOn ? $&quot;Jamming {mm}&quot; : &quot;Quiet time...&quot;);<\/p>\n<p>This method takes two parameters: a Boolean representing if the automobile\u2019s music system should be turned on or off and an enum representing the type of music player. Recall this enum was structured as so:<\/p>\n<p>public enum MusicMediaEnum<br \/>\n{<br \/>\nmusicCd,    \/\/ 0<br \/>\nmusicTape, \/\/ 1<br \/>\nmusicRadio, \/\/ 2<br \/>\nmusicMp3    \/\/ 3<br \/>\n}<\/p>\n<p>Here is a new method of the Program.cs file, which invokes TurnOnRadio(). Notice that you are using the underlying numerical values of the MusicMediaEnum enumeration to specify a \u201cradio\u201d media player.<\/p>\n<p>static void InvokeMethodWithArgsUsingLateBinding(Assembly asm)<br \/>\n{<br \/>\ntry<br \/>\n{<br \/>\n\/\/ First, get a metadata description of the sports car. Type sport = asm.GetType(&quot;CarLibrary.SportsCar&quot;);<\/p>\n<p>\/\/ Now, create the sports car.<br \/>\nobject obj = Activator.CreateInstance(sport);<br \/>\n\/\/ Invoke TurnOnRadio() with arguments. MethodInfo mi = sport.GetMethod(&quot;TurnOnRadio&quot;); mi.Invoke(obj, new object[] { true, 2 });<br \/>\n}<br \/>\ncatch (Exception ex)<br \/>\n{<br \/>\nConsole.WriteLine(ex.Message);<br \/>\n}<br \/>\n}<\/p>\n<p>Ideally, at this point, you can see the relationships among reflection, dynamic loading, and late binding. To be sure, the reflection API provides many additional features beyond what has been covered here, but you should be in good shape to dig into more details if you are interested.<br \/>\nAgain, you still might wonder exactly when you should use these techniques in your own applications.<br \/>\nThe extendable app later in this chapter should shed light on this issue; however, the next topic under investigation is the role of .NET attributes.<\/p>\n<p>Understanding the Role of .NET Attributes<br \/>\nAs illustrated at the beginning of this chapter, one role of a .NET compiler is to generate metadata descriptions for all defined and referenced types. In addition to this standard metadata contained within any assembly, the .NET platform provides a way for programmers to embed additional metadata into<br \/>\nan assembly using attributes. In a nutshell, attributes are nothing more than code annotations that can be applied to a given type (class, interface, structure, etc.), member (property, method, etc.), assembly, or module.<br \/>\n.NET attributes are class types that extend the abstract System.Attribute base class. As you explore the .NET namespaces, you will find many predefined attributes that you are able to use in your applications. Furthermore, you are free to build custom attributes to further qualify the behavior of your types by creating<\/p>\n<p>a new type deriving from Attribute. The .NET base class library provides attributes in various namespaces. Table 17-3 gives a snapshot of some\u2014but by absolutely no means all\u2014predefined attributes.<\/p>\n<p>Table 17-3. A Tiny Sampling of Predefined Attributes<\/p>\n<p>Attribute   Meaning in Life<\/p>\n<p>[CLSCompliant]  Enforces the annotated item to conform to the rules of the Common Language Specification (CLS). Recall that CLS-compliant types are guaranteed to be used seamlessly across all .NET programming languages.<br \/>\n[DllImport] Allows .NET code to make calls to any unmanaged C- or C++-based code library, including the API of the underlying operating system.<br \/>\n[Obsolete]  Marks a deprecated type or member. If other programmers attempt to use such an item, they will receive a compiler warning describing the error of their ways.<\/p>\n<p>Understand that when you apply attributes in your code, the embedded metadata is essentially useless until another piece of software explicitly reflects over the information. If this is not the case, the blurb of metadata embedded within the assembly is ignored and completely harmless.<\/p>\n<p>Attribute Consumers<br \/>\nAs you would guess, the .NET Framework ships with numerous utilities that are indeed on the lookout for various attributes. The C# compiler (csc.exe) itself has been preprogrammed to discover the presence<br \/>\nof various attributes during the compilation cycle. For example, if the C# compiler encounters the [CLSCompliant] attribute, it will automatically check the attributed item to ensure it is exposing only CLS- compliant constructs. By way of another example, if the C# compiler discovers an item attributed with the [Obsolete] attribute, it will display a compiler warning.<br \/>\nIn addition to development tools, numerous methods in the .NET base class libraries are preprogrammed to reflect over specific attributes. Chapter 19 introduces XML and JSON serialization, both of which use attributes to control the serialization process.<br \/>\nFinally, you are free to build applications that are programmed to reflect over your own custom attributes, as well as any attribute in the .NET base class libraries. By doing so, you are essentially able to create a set of \u201ckeywords\u201d that are understood by a specific set of assemblies.<\/p>\n<p>Applying Attributes in C#<br \/>\nTo illustrate the process of applying attributes in C#, create a new Console Application project named ApplyingAttributes and add a reference to the System.Text.Json NuGet package. Update the Program.cs file to include the following global using statements:<\/p>\n<p>global using System.Text.Json.Serialization; global using System.Xml.Serialization;<\/p>\n<p>Assume you want to build a class named Motorcycle that can be persisted to JSON format. If you have a field that should not be exported to JSON, you can apply the [JsonIgnore] attribute.<\/p>\n<p>namespace ApplyingAttributes; public class Motorcycle<br \/>\n{<\/p>\n<p>[JsonIgnore]<br \/>\npublic float weightOfCurrentPassengers;<br \/>\n\/\/ These fields are still serializable. public bool hasRadioSystem;<br \/>\npublic bool hasHeadSet; public bool hasSissyBar;<br \/>\n}<\/p>\n<p>\u25a0 Note  an attribute applies to the \u201cvery next\u201d item.<\/p>\n<p>At this point, do not concern yourself with the actual process of object serialization (again, Chapter 19 examines the details). Just notice that when you want to apply an attribute, the name of the attribute is sandwiched between square brackets.<br \/>\nAs you might guess, a single item can be attributed with multiple attributes. Assume you have a legacy C# class type (HorseAndBuggy) that was attributed to have a custom XML namespace. The code base has changed over time, and the class is now considered obsolete for current development. Rather than deleting the class definition from your code base (and risk breaking existing software), you can mark the class with the [Obsolete] attribute. To apply multiple attributes to a single item, simply use a comma-delimited list, like so:<\/p>\n<p>namespace ApplyingAttributes;<br \/>\n[XmlRoot(Namespace = &quot;<a href=\"http:\/\/www.MyCompany.com\"><a href=\"http:\/\/www.MyCompany.com\"><a href=\"http:\/\/www.MyCompany.com\">http:\/\/www.MyCompany.com<\/a><\/a><\/a>&quot;), Obsolete(&quot;Use another vehicle!&quot;)] public class HorseAndBuggy<br \/>\n{<br \/>\n\/\/ ...<br \/>\n}<\/p>\n<p>As an alternative, you can also apply multiple attributes on a single item by stacking each attribute as follows:<\/p>\n<p>[XmlRoot(Namespace = &quot;<a href=\"http:\/\/www.MyCompany.com\"><a href=\"http:\/\/www.MyCompany.com\"><a href=\"http:\/\/www.MyCompany.com\">http:\/\/www.MyCompany.com<\/a><\/a><\/a>&quot;)] [Obsolete(&quot;Use another vehicle!&quot;)]<br \/>\npublic class HorseAndBuggy<br \/>\n{<br \/>\n\/\/ ...<br \/>\n}<\/p>\n<p>C# Attribute Shorthand Notation<br \/>\nIf you were consulting the .NET documentation, you might have noticed that the actual class name of the [Obsolete] attribute is ObsoleteAttribute, not Obsolete. As a naming convention, all .NET attributes (including custom attributes you may create yourself) are suffixed with the Attribute token. However, to simplify the process of applying attributes, the C# language does not require you to type in the Attribute suffix. Given this, the following iteration of the HorseAndBuggy type is identical to the previous example (it just involves a few more keystrokes):<\/p>\n<p>[XmlRootAttribute(Namespace = &quot;<a href=\"http:\/\/www.MyCompany.com\"><a href=\"http:\/\/www.MyCompany.com\"><a href=\"http:\/\/www.MyCompany.com\">http:\/\/www.MyCompany.com<\/a><\/a><\/a>&quot;)] [ObsoleteAttribute(&quot;Use another vehicle!&quot;)]<br \/>\npublic class HorseAndBuggy<\/p>\n<p>{<br \/>\n\/\/ ...<br \/>\n}<\/p>\n<p>Be aware that this is a courtesy provided by C#. Not all .NET languages support this shorthand attribute syntax.<\/p>\n<p>Specifying Constructor Parameters for Attributes<br \/>\nNotice that the [Obsolete] attribute can accept what appears to be a constructor parameter. If you view the formal definition of the [Obsolete] attribute, you will find that this class indeed provides a constructor receiving a System.String.<\/p>\n<p>public sealed class ObsoleteAttribute : Attribute<br \/>\n{<br \/>\npublic ObsoleteAttribute();<br \/>\npublic ObsoleteAttribute(string? message);<br \/>\npublic ObsoleteAttribute(string? message, bool error); public string? Message { get; }<br \/>\npublic bool IsError { get; }<br \/>\npublic string DiagnosticId { get; set; } public string UrlFormat { get; set; }<br \/>\n}<\/p>\n<p>Understand that when you supply constructor parameters to an attribute, the attribute is not allocated into memory until the parameters are reflected upon by another type or an external tool. The string data defined at the attribute level is simply stored within the assembly as a blurb of metadata.<\/p>\n<p>The Obsolete Attribute in Action<br \/>\nNow that HorseAndBuggy has been marked as obsolete, if you were to allocate an instance of this type:<\/p>\n<p>using ApplyingAttributes;<\/p>\n<p>Console.WriteLine(&quot;Hello World!&quot;); HorseAndBuggy mule = new HorseAndBuggy();<\/p>\n<p>you would find that a compiler warning is issued. The warning is specifically CS0618, and the message includes the information passed into the attribute.<\/p>\n<p>'HorseAndBuggy' is obsolete: 'Use another vehicle!'<\/p>\n<p>Visual Studio and Visual Studio Code also help with IntelliSense, which gets their information through reflection. Figure 17-1 shows the results of the Obsolete attribute in Visual Studio through IntelliSense, and Figure 17-2 shows the more detailed messaging in the Visual Studio code editor. Note that both use the term deprecated instead of obsolete.<\/p>\n<p>Figure 17-1. Attributes in action in Visual Studio<\/p>\n<p>Figure 17-2. Hovering over Obsolete types in the Visual Studio editor window<\/p>\n<p>Figure 17-3 and Figure 17-4 show the results of the Obsolete attribute in Visual Studio Code.<\/p>\n<p>Figure 17-3. Attributes in action in Visual Studio Code<\/p>\n<p>Figure 17-4. Hovering over Obsolete types in the Visual Studio Code editor<\/p>\n<p>Ideally, at this point, you should understand the following key points regarding .NET attributes:<br \/>\n\u2022Attributes are classes that derive from System.Attribute.<br \/>\n\u2022Attributes result in embedded metadata.<br \/>\n\u2022Attributes are basically useless until another agent (including IDEs) reflects upon them.<br \/>\n\u2022Attributes are applied in C# using square brackets.<\/p>\n<p>Next up, let\u2019s examine how you can build your own custom attributes and a piece of custom software that reflects over the embedded metadata.<\/p>\n<p>Building Custom Attributes<br \/>\nThe first step in building a custom attribute is to create a new class deriving from System.Attribute. Keeping in step with the automobile theme used throughout this book, assume you have created a new C# Class Library project named AttributedCarLibrary.<br \/>\nThis assembly will define a handful of vehicles, each of which is described using a custom attribute named VehicleDescriptionAttribute, as follows:<\/p>\n<p>namespace AttributedCarLibrary;<br \/>\n\/\/ A custom attribute.<br \/>\npublic sealed class VehicleDescriptionAttribute :Attribute<br \/>\n{<br \/>\npublic string Description { get; set; }<\/p>\n<p>public VehicleDescriptionAttribute(string description)<br \/>\n=&gt; Description = description;<br \/>\npublic VehicleDescriptionAttribute(){ }<br \/>\n}<\/p>\n<p>As you can see, VehicleDescriptionAttribute maintains a piece of string data using an automatic property (Description). Beyond the fact that this class derived from System.Attribute, there is nothing unique to this class definition.<\/p>\n<p>\u25a0 Note  for security reasons, it is considered a .NET best practice to design all custom attributes as sealed. in fact, both Visual studio and Visual studio Code provide a code snippet named Attribute that will scaffold a new System.Attribute-derived class into your code window. you can expand any snippet by typing its name and pressing the Tab key.<\/p>\n<p>Applying Custom Attributes<br \/>\nGiven that VehicleDescriptionAttribute is derived from System.Attribute, you are now able to annotate your vehicles as you see fit. For testing purposes, add the following classes to your new class library:<\/p>\n<p>\/\/Motorcycle.cs<br \/>\nnamespace AttributedCarLibrary;<br \/>\n\/\/ Assign description using a &quot;named property.&quot; [VehicleDescription(Description = &quot;My rocking Harley&quot;)] public class Motorcycle<br \/>\n{<br \/>\n}<\/p>\n<p>\/\/HorseAndBuggy.cs<br \/>\nnamespace AttributedCarLibrary; [Obsolete (&quot;Use another vehicle!&quot;)]<\/p>\n<p>[VehicleDescription(&quot;The old gray mare, she ain't what she used to be...&quot;)] public class HorseAndBuggy<br \/>\n{<br \/>\n}<\/p>\n<p>\/\/Winnebago.cs<br \/>\nnamespace AttributedCarLibrary;<br \/>\n[VehicleDescription(&quot;A very long, slow, but feature-rich auto&quot;)] public class Winnebago<br \/>\n{<br \/>\n}<\/p>\n<p>Named Property Syntax<br \/>\nNotice that the description of Motorcycle is assigned a description using a new bit of attribute syntax termed a named property. In the constructor of the first [VehicleDescription] attribute, you set the underlying string data by using the Description property. If this attribute is reflected upon by an external agent, the value is fed into the Description property (named property syntax is legal only if the attribute supplies a writable .NET property).<br \/>\nIn contrast, the HorseAndBuggy and Winnebago types are not using named property syntax and are simply passing the string data via the custom constructor. In any case, once you compile the<br \/>\nAttributedCarLibrary assembly, you can use ildasm.exe to view the injected metadata descriptions for your type. For example, the following shows the embedded description of the Winnebago class:<\/p>\n<p>\/\/ CustomAttribute #1<br \/>\n\/\/<br \/>\n\/\/  CustomAttribute Type: 06000005<br \/>\n\/\/  CustomAttributeName: AttributedCarLibrary.VehicleDescriptionAttribute :: instance void<br \/>\n.ctor(class System.String)<br \/>\n\/\/  Length: 45<br \/>\n\/\/  Value : 01 00 28 41 20 76 65 72 79 20 6c 6f 6e 67 2c 20 &gt; (A very long, &lt;<br \/>\n\/\/  : 73 6c 6f 77 2c 20 62 75 74 20 66 65 61 74 75 72 &gt;slow, but feature&lt;<br \/>\n\/\/  : 65 2d 72 69 63 68 20 61 75 74 6f 00 00    &gt;e-rich auto    &lt;<br \/>\n\/\/  ctor args: (&quot;A very long, slow, but feature-rich auto&quot;)<\/p>\n<p>Restricting Attribute Usage<br \/>\nBy default, custom attributes can be applied to just about any aspect of your code (methods, classes, properties, etc.). Thus, if it made sense to do so, you could use VehicleDescription to qualify methods, properties, or fields (among other things).<\/p>\n<p>[VehicleDescription(&quot;A very long, slow, but feature-rich auto&quot;)] public class Winnebago<br \/>\n{<br \/>\n[VehicleDescription(&quot;My rocking CD player&quot;)] public void PlayMusic(bool On)<br \/>\n{<br \/>\n...<br \/>\n}<br \/>\n}<\/p>\n<p>In some cases, this is exactly the behavior you require. Other times, however, you may want to build a custom attribute that can be applied only to select code elements. If you want to constrain the scope of a custom attribute, you will need to apply the [AttributeUsage] attribute on the definition of your custom attribute. The [AttributeUsage] attribute allows you to supply any combination of values (via an OR operation) from the AttributeTargets enumeration, like so:<\/p>\n<p>\/\/ This enumeration defines the possible targets of an attribute. public enum AttributeTargets<br \/>\n{<br \/>\nAll, Assembly, Class, Constructor,<br \/>\nDelegate, Enum, Event, Field, GenericParameter, Interface, Method, Module, Parameter,<br \/>\nProperty, ReturnValue, Struct<br \/>\n}<\/p>\n<p>Furthermore, [AttributeUsage] also allows you to optionally set a named property (AllowMultiple) that specifies whether the attribute can be applied more than once on the same item (the default is false). As well, [AttributeUsage] allows you to establish whether the attribute should be inherited by derived classes using the Inherited named property (the default is true).<br \/>\nTo establish that the [VehicleDescription] attribute can be applied only once on a class or structure, you can update the VehicleDescriptionAttribute definition as follows:<\/p>\n<p>\/\/ This time, we are using the AttributeUsage attribute<br \/>\n\/\/ to annotate our custom attribute.<br \/>\n[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)] public sealed class VehicleDescriptionAttribute : System.Attribute<br \/>\n{<br \/>\n...<br \/>\n}<\/p>\n<p>With this, if a developer attempted to apply the [VehicleDescription] attribute on anything other than a class or structure, they are issued a compile-time error.<\/p>\n<p>Assembly-Level Attributes<br \/>\nIt is also possible to apply attributes on all types within a given assembly using the [assembly:] tag. For example, assume you want to ensure that every public member of every public type defined within your assembly is CLS compliant. To do so, simply add the following assembly-level attribute at the top of any C# source code file, like this (outside of any namespace declarations):<\/p>\n<p>[assembly: CLSCompliant] namespace AttributedCarLibrary;<br \/>\n...<\/p>\n<p>\u25a0 Note all assembly-level or module-level attributes must be listed outside the scope of any namespace scope.<\/p>\n<p>Using a Separate File for Assembly Attributes<br \/>\nAnother approach is to add a new file to your project named similar to AssemblyAttributes.cs (any name will work, but that name conveys the purpose of the file) and place your assembly-level attributes in that file. In the .NET Framework, it was recommended to use AssemblyInfo.cs; however, with .NET (Core), that file can\u2019t be used. Placing the attributes in a separate file will make it easier for other developers to find the attributes applied to the project.<\/p>\n<p>\u25a0 Note There are two significant changes in .NET. The first is that the AssemblyInfo.cs file is now autogenerated from the project properties and is not meant for developer use. The second (and related) change is that many of the prior assembly-level attributes (Version, Company, etc.) have been replaced with properties in the project file.<\/p>\n<p>Create a new file named AssemblyAttributes.cs and update it to match the following:<\/p>\n<p>\/\/ List &quot;using&quot; statements first.<\/p>\n<p>\/\/ Now list any assembly- or module-level attributes.<br \/>\n\/\/ Enforce CLS compliance for all public types in this<br \/>\n\/\/ assembly.<br \/>\n[assembly: CLSCompliant(true)]<\/p>\n<p>If you now add a bit of code that falls outside the CLS specification (such as an exposed point of unsigned data), you will be issued a compiler warning.<\/p>\n<p>\/\/ Ulong types don't jibe with the CLS. public class Winnebago<br \/>\n{<br \/>\npublic ulong notCompliant;<br \/>\n}<\/p>\n<p>Using the Project File for Assembly Attributes<br \/>\nAs shown in Chapter 16 with InternalsVisibleToAttribute, assembly attributes can also be added to the project file. There is a catch, in that only single-string parameter attributes can be used this way. This is true for the properties that can be set on the Package tab in the project properties.<\/p>\n<p>\u25a0 Note at the time of this writing, there is an active discussion on the msBuild github repo regarding adding capability for nonstring parameters support. This would enable the CLSCompliant attribute to be added using the project file instead of a *.cs file.<\/p>\n<p>Go ahead and set some of the properties (such as Authors, Description) by right-clicking the project in Solution Explorer, selecting Properties, and then clicking Package. Also, add InternalsVisibleToAttribute as you did in Chapter 16. Your project file will now look something like this:<\/p>\n<p><Project Sdk=\"Microsoft.NET.Sdk\"><\/p>\n<p><PropertyGroup><br \/>\n<TargetFramework>net6.0<\/TargetFramework><br \/>\n<ImplicitUsings>enable<\/ImplicitUsings><br \/>\n<Nullable>disable<\/Nullable><br \/>\n<Authors>Philip Japikse<\/Authors><br \/>\n<Company>Apress<\/Company><br \/>\n<Description>This is a simple car library with attributes<\/Description><br \/>\n<\/PropertyGroup><br \/>\n<\/Project><\/p>\n<p>After you compile your project, navigate to the \\obj\\Debug\\net6.0 directory, and look for the AttributedCarLibrary.AssemblyInfo.cs file. Open that, and you will see those properties as attributes (unfortunately, not very readable in this format).<\/p>\n<p>using System;<br \/>\nusing System.Reflection;<\/p>\n<p>[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute(&quot;CSharpCarClient&quot;)] [assembly: System.Reflection.AssemblyCompanyAttribute(&quot;Philip Japikse&quot;)]<br \/>\n[assembly: System.Reflection.AssemblyConfigurationAttribute(&quot;Debug&quot;)]<br \/>\n[assembly: System.Reflection.AssemblyDescriptionAttribute(&quot;This is a sample car library with attributes&quot;)]<br \/>\n[assembly: System.Reflection.AssemblyFileVersionAttribute(&quot;1.0.0.0&quot;)] [assembly: System.Reflection.AssemblyInformationalVersionAttribute(&quot;1.0.0&quot;)] [assembly: System.Reflection.AssemblyProductAttribute(&quot;AttributedCarLibrary&quot;)] [assembly: System.Reflection.AssemblyTitleAttribute(&quot;AttributedCarLibrary&quot;)] [assembly: System.Reflection.AssemblyVersionAttribute(&quot;1.0.0.0&quot;)]<\/p>\n<p>As a final closing remark on assembly attributes, you can turn off the generation of the AssemblyInfo. cs class if you want to manage the process yourself.<\/p>\n<p>Reflecting on Attributes Using Early Binding<br \/>\nRemember that an attribute is quite useless until another piece of software reflects over its values. Once a given attribute has been discovered, that piece of software can take whatever course of action necessary. Now, like any application, this \u201cother piece of software\u201d could discover the presence of a custom attribute using either early binding or late binding. If you want to make use of early binding, you\u2019ll require the client application to have a compile-time definition of the attribute in question (VehicleDescriptionAttribute, in this case). Given that the AttributedCarLibrary assembly has defined this custom attribute as a public class, early binding is the best option.<\/p>\n<p>To illustrate the process of reflecting on custom attributes, add a new C# Console Application project named VehicleDescriptionAttributeReader to the solution. Next, add a reference to the AttributedCarLibrary project. Using the CLI, execute these commands (each command must be on its own line):<\/p>\n<p>dotnet new console -lang c# -n VehicleDescriptionAttributeReader -o .\\ VehicleDescriptionAttributeReader -f net6.0<br \/>\ndotnet sln .\\Chapter17_AllProjects.sln add .\\VehicleDescriptionAttributeReader dotnet add VehicleDescriptionAttributeReader reference .\\AttributedCarLibrary<\/p>\n<p>Update the Program.cs file with the following code:<\/p>\n<p>using AttributedCarLibrary;<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Value of VehicleDescriptionAttribute <\/em><\/strong><\/strong>\\n&quot;); ReflectOnAttributesUsingEarlyBinding();<br \/>\nConsole.ReadLine();<\/p>\n<p>static void ReflectOnAttributesUsingEarlyBinding()<br \/>\n{<br \/>\n\/\/ Get a Type representing the Winnebago. Type t = typeof(Winnebago);<\/p>\n<p>\/\/ Get all attributes on the Winnebago.<br \/>\nobject[] customAtts = t.GetCustomAttributes(false);<\/p>\n<p>\/\/ Print the description.<br \/>\nforeach (VehicleDescriptionAttribute v in customAtts)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;-&gt; {0}\\n&quot;, v.Description);<br \/>\n}<br \/>\n}<\/p>\n<p>The Type.GetCustomAttributes() method returns an object array that represents all the attributes applied to the member represented by the Type (the Boolean parameter controls whether the search should extend up the inheritance chain). Once you have obtained the list of attributes, iterate over each VehicleDescriptionAttribute class, and print out the value obtained by the Description property.<\/p>\n<p>Reflecting on Attributes Using Late Binding<br \/>\nThe previous example used early binding to print out the vehicle description data for the Winnebago type. This was possible because the VehicleDescriptionAttribute class type was defined as a public member in the AttributedCarLibrary assembly. It is also possible to make use of dynamic loading and late binding to reflect over attributes.<br \/>\nAdd a new project called VehicleDescriptionAttributeReaderLateBinding to the solution, set it as the startup project, and copy AttributedCarLibrary.dll to the project\u2019s folder (or \\bin\\Debug\\net6.0 if using Visual Studio). Now, update your Program.cs file as follows:<\/p>\n<p>using System.Reflection;<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Value of VehicleDescriptionAttribute <\/em><\/strong><\/strong>\\n&quot;);<\/p>\n<p>ReflectAttributesUsingLateBinding(); Console.ReadLine();<\/p>\n<p>static void ReflectAttributesUsingLateBinding()<br \/>\n{<br \/>\ntry<br \/>\n{<br \/>\n\/\/ Load the local copy of AttributedCarLibrary.<br \/>\nAssembly asm = Assembly.LoadFrom(&quot;AttributedCarLibrary&quot;);<\/p>\n<p>\/\/ Get type info of VehicleDescriptionAttribute.<br \/>\nType vehicleDesc = asm.GetType(&quot;AttributedCarLibrary.VehicleDescriptionAttribute&quot;);<\/p>\n<p>\/\/ Get type info of the Description property.<br \/>\nPropertyInfo propDesc = vehicleDesc.GetProperty(&quot;Description&quot;);<\/p>\n<p>\/\/ Get all types in the assembly.<br \/>\nType[] types = asm.GetTypes();<\/p>\n<p>\/\/ Iterate over each type and obtain any VehicleDescriptionAttributes.<br \/>\nforeach (Type t in types)<br \/>\n{<br \/>\nobject[] objs = t.GetCustomAttributes(vehicleDesc, false);<\/p>\n<p>\/\/ Iterate over each VehicleDescriptionAttribute and print<br \/>\n\/\/ the description using late binding.<br \/>\nforeach (object o in objs)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;-&gt; {0}: {1}\\n&quot;, t.Name, propDesc.GetValue(o, null));<br \/>\n}<br \/>\n}<br \/>\n}<br \/>\ncatch (Exception ex)<br \/>\n{<br \/>\nConsole.WriteLine(ex.Message);<br \/>\n}<br \/>\n}<\/p>\n<p>If you were able to follow along with the examples in this chapter, this code should be (more or less) self-explanatory. The only point of interest is the use of the PropertyInfo.GetValue() method, which is used to trigger the property\u2019s accessor. Here is the output of the current example:<\/p>\n<p><strong><strong><em> Value of VehicleDescriptionAttribute <\/em><\/strong><\/strong><br \/>\n-&gt; Motorcycle: My rocking Harley<\/p>\n<p>-&gt; HorseAndBuggy: The old gray mare, she ain't what she used to be...<\/p>\n<p>-&gt; Winnebago: A very long, slow, but feature-rich auto<\/p>\n<p>Putting Reflection, Late Binding, and Custom Attributes in Perspective<br \/>\nEven though you have seen numerous examples of these techniques in action, you may still be wondering when to make use of reflection, dynamic loading, late binding, and custom attributes in your programs. To be sure, these topics can seem a bit on the academic side of programming (which may or may not be a bad thing, depending on your point of view). To help map these topics to a real-world situation, you need a solid example. Assume for the moment that you are on a programming team that is building an application with the following requirement:<br \/>\nThe product must be extendable using additional third-party tools.<br \/>\nWhat exactly is meant by extendable? Well, consider the Visual Studio or Visual Studio Code IDEs. When this application was developed, various \u201chooks\u201d were inserted into the code base to allow other software vendors to \u201csnap\u201d (or plug in) custom modules into the IDE. Obviously, the Visual Studio\/Visual Studio Code development teams had no way to set references to external .NET assemblies that had not been developed yet (thus, no early binding), so how exactly would an application provide the required hooks? Here is one possible way to solve this problem:<br \/>\n1.First, an extendable application must provide some input mechanism to allow the user to specify the module to plug in (such as a dialog box or command-line flag). This requires dynamic loading.<br \/>\n2.Second, an extendable application must be able to determine whether the module supports the correct functionality (such as a set of required interfaces) to be plugged into the environment. This requires reflection.<br \/>\n3.Finally, an extendable application must obtain a reference to the required infrastructure (such as a set of interface types) and invoke the members to trigger the underlying functionality. This may require late binding.<br \/>\nSimply put, if the extendable application has been preprogrammed to query for specific interfaces, it is able to determine at runtime whether the type can be activated. Once this verification test has been passed, the type in question may support additional interfaces that provide a polymorphic fabric to their functionality. This is the exact approach taken by the Visual Studio team and, despite what you might be thinking, is not at all difficult!<\/p>\n<p>Building an Extendable Application<br \/>\nIn the sections that follow, I will take you through an example that illustrates the process of building an application that can be augmented by the functionality of external assemblies. To serve as a road map, the extendable application entails the following assemblies:<br \/>\n\u2022CommonSnappableTypes.dll: This assembly contains type definitions that will be used by each snap-in object and will be directly referenced by the Windows Forms application.<br \/>\n\u2022CSharpSnapIn.dll: A snap-in written in C#, which leverages the types of<br \/>\nCommonSnappableTypes.dll.<br \/>\n\u2022VBSnapIn.dll: A snap-in written in Visual Basic, which leverages the types of<br \/>\nCommonSnappableTypes.dll.<br \/>\n\u2022MyExtendableApp.exe: A console application that may be extended by the functionality of each snap-in.<\/p>\n<p>This application will use dynamic loading, reflection, and late binding to dynamically gain the functionality of assemblies it has no prior knowledge of.<\/p>\n<p>\u25a0 Note you might be thinking to yourself, \u201cmy boss has never asked me to build a console application,\u201d and you are probably correct! line-of-business applications built with C# usually fall into the category of smart client (Winforms or Wpf), web applications\/rEsTful services (asp.NET Core), or headless processes (azure functions, Windows services, etc.). We are using console applications to focus on the specific concepts of the example, in this case dynamic loading, reflection, and late binding. later in this book, you will explore \u201creal\u201d user-facing applications using Wpf and asp.NET Core.<\/p>\n<p>Building the Multiproject ExtendableApp Solution<br \/>\nUp to this point in this book, most of the applications have been stand-alone projects, with a few exceptions (like the previous one). This was done to keep the examples simple and focused. However, in real-world development, you typically work with multiple projects together in a solution.<\/p>\n<p>Creating the Solution and Projects with the CLI<br \/>\nTo get started using the CLI, enter the following commands to create a new solution, the class libraries and console application, and the project references:<\/p>\n<p>dotnet new sln -n Chapter17_ExtendableApp<\/p>\n<p>dotnet new classlib -lang c# -n CommonSnappableTypes -o .\\CommonSnappableTypes -f net6.0 dotnet sln .\\Chapter17_ExtendableApp.sln add .\\CommonSnappableTypes<\/p>\n<p>dotnet new classlib -lang c# -n CSharpSnapIn -o .\\CSharpSnapIn -f net6.0 dotnet sln .\\Chapter17_ExtendableApp.sln add .\\CSharpSnapIn<br \/>\ndotnet add CSharpSnapin reference CommonSnappableTypes<\/p>\n<p>dotnet new classlib -lang vb -n VBSnapIn -o .\\VBSnapIn -f net6.0 dotnet sln .\\Chapter17_ExtendableApp.sln add .\\VBSnapIn<br \/>\ndotnet add VBSnapIn reference CommonSnappableTypes<\/p>\n<p>dotnet new console -lang c# -n MyExtendableApp -o .\\MyExtendableApp -f net6.0 dotnet sln .\\Chapter17_ExtendableApp.sln add .\\MyExtendableApp<br \/>\ndotnet add MyExtendableApp reference CommonSnappableTypes<\/p>\n<p>Adding PostBuild Events into the Project Files<br \/>\nWhen projects are built (either from Visual Studio or from the command line), there are events that can be hooked into. For example, we want to copy the two snap-in assemblies into the console app project directory (when debugging with dotnet run) and the console app output directory (when debugging with Visual Studio) after every successful build. To do this, we are going to take advantage of several built-in macros.<\/p>\n<p>If you are using Visual Studio, copy this block of markup into the CSharpSnapIn.csproj and VBSnapIn.vbproj files, which copies the compiled assembly into the MyExtendableApp output directory (MyExtendableApp\\bin\\debug\\net6.0):<\/p>\n<p><Target Name=\"PostBuild\" AfterTargets=\"PostBuildEvent\"><br \/>\n<Exec Command=\"copy $(TargetPath) $(SolutionDir)MyExtendableApp\\$(OutDir)$(TargetFile Name) \/Y \" \/><br \/>\n<\/Target><\/p>\n<p>If you are using Visual Studio Code, copy this block of markup into the CSharpSnapIn.csproj and<br \/>\nVBSnapIn.vbproj files, which copies the compiled assembly into the MyExtendableApp project directory:<\/p>\n<p><Target Name=\"PostBuild\" AfterTargets=\"PostBuildEvent\"><br \/>\n<Exec Command=\"copy $(TargetPath) $(ProjectDir)..\\MyExtendableApp\\$(TargetFileName)\n\/Y \" \/><br \/>\n<\/Target><\/p>\n<p>Now when each project is built, its assembly is copied into the MyExtendableApp\u2019s target directory as well.<\/p>\n<p>Creating the Solution and Projects with Visual Studio<br \/>\nRecall that, by default, Visual Studio names the solution the same as the first project created in that solution. However, you can easily change the name of the solution, as shown in Figure 17-5.<\/p>\n<p>Figure 17-5. Creating the CommonSnappableTypes project and the ExtendableApp solution<\/p>\n<p>To create the ExtendableApp solution, start by selecting File \u27a4 New Project to load the New Project dialog. Select Class Library and enter the name CommonSnappableTypes. Before you click OK, enter the solution name ExtendableApp, as shown in Figure 17-5.<br \/>\nTo add another project into the solution, right-click the solution name (ExtendableApp) in Solution Explorer (or click File \u27a4 Add \u27a4 New Project) and select Add \u27a4 New Project. When adding another project into an existing solution, the Add New Project dialog box is a little different now; the solution options are no longer there, so you will see just the project information (name and location). Name the Class Library project CSharpSnapIn and click Create.<br \/>\nNext, add a reference to the CommonSnappableTypes project from the CSharpSnapIn project. To do this in Visual Studio, right-click the CSharpSnapIn project and select Add \u27a4 Project Reference. In the<br \/>\nReference Manager dialog, select Projects \u27a4 Solution from the left (if not already selected) and select the box<br \/>\nnext to CommonSnappableTypes.<br \/>\nRepeat the process for a new Visual Basic class library (VBSnapIn) that references the CommonSnappableTypes project.<br \/>\nThe final project to add is a .NET Console app named MyExtendableApp. Add a reference to the CommonSnappableTypes project and set the console app as the startup project for the solution. To do this, right-click the MyExtendableApp project in Solution Explorer and select Set as StartUp Project.<\/p>\n<p>\u25a0 Note  if you right-click the Extendableapp solution instead of one of the projects, the context menu option displayed is set startup projects. in addition to having just one project execute when you click run, you can set up multiple projects to execute. This will be demonstrated in later chapters.<\/p>\n<p>Setting Project Build Dependencies<br \/>\nWhen Visual Studio is given the command to run a solution, the startup projects and all referenced projects are built if any changes are detected; however, any unreferenced projects are not built. This can be changed by setting project dependencies. To do this, right-click the solution in Solution Explorer, select Project Build Order, and, in the resulting dialog, select the Dependencies tab and change the project to MyExtendableApp.<br \/>\nNotice that the CommonSnappableTypes project is already selected and the check box is disabled. This is because it is referenced directly. Select the CSharpSnapIn and VBSnapIn project check boxes as well, as shown in Figure 17-6.<\/p>\n<p>Figure 17-6. Accessing the Project Build Order context menu<\/p>\n<p>Now, each time the MyExtendableApp project is built, the CSharpSnapIn and VBSnapIn projects build as well.<\/p>\n<p>Adding PostBuild Events<br \/>\nOpen the project properties for the CSharpSnapIn (right-click Solution Explorer and select Properties) and navigate to the Build Events page (C#). Click the Edit Post-build button and then click Macros&gt;&gt;. Here you can see the macros available for use, and they all refer to paths and\/or filenames. The advantage of using these macros in build events is that they are machine independent and work on the relative paths. For example, I am working in a directory called c-sharp-wf\\code\\chapter17. You might be (and probably are) using a different directory. By using the macros, MSBuild will always use the correct path relative to the<br \/>\n*.csproj files.<br \/>\nIn the PostBuild box, enter the following (over two lines):<\/p>\n<p>copy $(TargetPath) $(SolutionDir)MyExtendableApp\\$(OutDir)$(TargetFileName) \/Y copy $(TargetPath) $(SolutionDir)MyExtendableApp\\$(TargetFileName) \/Y<\/p>\n<p>Do the same for the VBSnapIn project, except the property page is called Compile, and from there you can click the Build Events button.<br \/>\nAfter you have added these post-build event commands, each assembly will be copied into the MyExtendableApp\u2019s project and output directories each time they are compiled.<\/p>\n<p>Building CommonSnappableTypes.dll<br \/>\nIn the CommonSnappableTypes project, delete the default Class1.cs file, add a new interface file named<br \/>\nIAppFunctionality.cs, and update the file to the following:<\/p>\n<p>namespace CommonSnappableTypes; public interface IAppFunctionality<br \/>\n{<br \/>\nvoid DoIt();<br \/>\n}<\/p>\n<p>Add a class file named CompanyInfoAttribute.cs and update it to the following:<\/p>\n<p>namespace CommonSnappableTypes; [AttributeUsage(AttributeTargets.Class)]<br \/>\npublic sealed class CompanyInfoAttribute : System.Attribute<br \/>\n{<br \/>\npublic string CompanyName { get; set; } public string CompanyUrl { get; set; }<br \/>\n}<\/p>\n<p>The IAppFunctionality interface provides a polymorphic interface for all snap-ins that can be consumed by the extendable application. Given that this example is purely illustrative, you supply a single method named DoIt().<br \/>\nThe CompanyInfoAttribute type is a custom attribute that can be applied on any class type that wants to be snapped into the container. As you can tell by the definition of this class, [CompanyInfo] allows the developer of the snap-in to provide some basic details about the component\u2019s point of origin.<\/p>\n<p>Building the C# Snap-In<br \/>\nIn the CSharpSnapIn project, delete the Class1.cs file and add a new file named CSharpModule.cs. Update the code to match the following:<br \/>\nusing CommonSnappableTypes; namespace CSharpSnapIn;<br \/>\n[CompanyInfo(CompanyName = &quot;FooBar&quot;, CompanyUrl = &quot;www.FooBar.com&quot;)] public class CSharpModule : IAppFunctionality<br \/>\n{<br \/>\nvoid IAppFunctionality.DoIt()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;You have just used the C# snap-in!&quot;);<br \/>\n}<br \/>\n}<\/p>\n<p>Notice that I chose to make use of explicit interface implementation (see Chapter 8) when supporting the IAppFunctionality interface. This is not required; however, the idea is that the only part of the system that needs to directly interact with this interface type is the hosting application. By explicitly implementing this interface, the DoIt() method is not directly exposed from the CSharpModule type.<\/p>\n<p>Building the Visual Basic Snap-In<br \/>\nMoving on to the VBSnapIn project, delete the Class1.vb file and add a new file named VBSnapIn.vb. The code is (again) intentionally simple.<\/p>\n<p>Imports CommonSnappableTypes<\/p>\n<p>&lt;CompanyInfo(CompanyName:=&quot;Chucky's Software&quot;, CompanyUrl:=&quot;www.ChuckySoft.com&quot;)&gt; Public Class VBSnapIn<br \/>\nImplements IAppFunctionality<\/p>\n<p>Public Sub DoIt() Implements CommonSnappableTypes.IAppFunctionality.DoIt Console.WriteLine(&quot;You have just used the VB snap in!&quot;)<br \/>\nEnd Sub End Class<\/p>\n<p>Notice that applying attributes in the syntax of Visual Basic requires angle brackets (&lt; &gt;) rather than square brackets ([ ]). Also notice that the Implements keyword is used to implement interface types on a given class or structure.<\/p>\n<p>Adding the Code for the ExtendableApp<br \/>\nThe final project to update is the C# console application (MyExtendableApp). After adding the MyExtendableApp console application to the solution and setting it as the startup project, add a reference to the CommonSnappableTypes project but not the CSharpSnapIn.dll or VBSnapIn.dll project.<br \/>\nBegin by updating the using statements at the top of the Program.cs class to the following:<\/p>\n<p>using System.Reflection; using CommonSnappableTypes;<\/p>\n<p>The LoadExternalModule() method performs the following tasks:<br \/>\n\u2022   Dynamically loads the selected assembly into memory<br \/>\n\u2022   Determines whether the assembly contains any types implementing<br \/>\nIAppFunctionality<br \/>\n\u2022   Creates the type using late binding<br \/>\nIf a type implementing IAppFunctionality is found, the DoIt() method is called and then sent to the<br \/>\nDisplayCompanyData() method to output additional information from the reflected type.<\/p>\n<p>static void LoadExternalModule(string assemblyName)<br \/>\n{<br \/>\nAssembly theSnapInAsm = null; try<br \/>\n{<br \/>\n\/\/ Dynamically load the selected assembly. theSnapInAsm = Assembly.LoadFrom(assemblyName);<br \/>\n}<br \/>\ncatch (Exception ex)<br \/>\n{<\/p>\n<p>Console.WriteLine($&quot;An error occurred loading the snapin: {ex.Message}&quot;); return;<br \/>\n}<\/p>\n<p>\/\/ Get all IAppFunctionality compatible classes in assembly. var theClassTypes = theSnapInAsm<br \/>\n.GetTypes()<br \/>\n.Where(t =&gt; t.IsClass &amp;&amp; (t.GetInterface(&quot;IAppFunctionality&quot;) != null))<br \/>\n.ToList();<br \/>\nif (!theClassTypes.Any())<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Nothing implements IAppFunctionality!&quot;);<br \/>\n}<\/p>\n<p>\/\/ Now, create the object and call DoIt() method. foreach (Type t in theClassTypes)<br \/>\n{<br \/>\n\/\/ Use late binding to create the type.<br \/>\nIAppFunctionality itfApp = (IAppFunctionality) theSnapInAsm.CreateInstance (t.FullName, true);<br \/>\nitfApp?.DoIt();<br \/>\n\/\/ Show company info.<br \/>\nDisplayCompanyData(t);<br \/>\n}<br \/>\n}<\/p>\n<p>The final task is to display the metadata provided by the [CompanyInfo] attribute. Create the<br \/>\nDisplayCompanyData() method as follows. Notice this method takes a single System.Type parameter.<\/p>\n<p>static void DisplayCompanyData(Type t)<br \/>\n{<br \/>\n\/\/ Get [CompanyInfo] data. var compInfo = t<br \/>\n.GetCustomAttributes(false)<br \/>\n.Where(ci =&gt; (ci is CompanyInfoAttribute));<br \/>\n\/\/ Show data.<br \/>\nforeach (CompanyInfoAttribute c in compInfo)<br \/>\n{<br \/>\nConsole.WriteLine($&quot;More info about {c.CompanyName} can be found at {c.CompanyUrl}&quot;);<br \/>\n}<br \/>\n}<\/p>\n<p>Finally, update the top-level statements to the following:<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Welcome to MyTypeViewer <\/em><\/strong><\/strong>&quot;); string typeName = &quot;&quot;;<br \/>\ndo<br \/>\n{<br \/>\nConsole.WriteLine(&quot;\\nEnter a snapin to load&quot;); Console.Write(&quot;or enter Q to quit: &quot;);<\/p>\n<p>\/\/ Get name of type.<br \/>\ntypeName = Console.ReadLine();<\/p>\n<p>\/\/ Does user want to quit?<br \/>\nif (typeName.Equals(&quot;Q&quot;, StringComparison.OrdinalIgnoreCase))<br \/>\n{<br \/>\nbreak;<br \/>\n}<br \/>\n\/\/ Try to display type. try<br \/>\n{<br \/>\nLoadExternalModule(typeName);<br \/>\n}<br \/>\ncatch (Exception ex)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Sorry, can't find snapin&quot;);<br \/>\n}<br \/>\n}<br \/>\nwhile (true);<\/p>\n<p>That wraps up the example application. When you run the application, type in either VBSnapIn or CSharpSnapIn and see the program in action. Note that while C# is case sensitive, when using reflection, case doesn\u2019t matter. Both CSharpSnapIn and csharpsnapin work the equally as well.<br \/>\nThis concludes our look at late binding. Next up is exploring dynamics in C#.<\/p>\n<p>The Role of the C# dynamic Keyword<br \/>\nIn Chapter 3, you learned about the var keyword, which allows you to define local variables in such a way that the underlying data type is determined at compile time, based on the initial assignment (recall that this is termed implicit typing). Once this initial assignment has been made, you have a strongly typed variable, and any attempt to assign an incompatible value will result in a compiler error.<br \/>\nTo begin your investigation into the C# dynamic keyword, create a new Console Application project named DynamicKeyword. Now, add the following method in your Program.cs file, and verify that the final code statement will indeed trigger a compile-time error if uncommented:<\/p>\n<p>static void ImplicitlyTypedVariable()<br \/>\n{<br \/>\n\/\/ a is of type List<int>. var a = new List<int> {90};<br \/>\n\/\/ This would be a compile-time error!<br \/>\n\/\/ a = &quot;Hello&quot;;<br \/>\n}<\/p>\n<p>As you saw in Chapter 13, implicit typing is useful with LINQ, as many LINQ queries return enumerations of anonymous classes (via projections) that you cannot directly declare in your C# code. However, even in such cases, the implicitly typed variable is, in fact, strongly typed. In the previous example, the variable a is strongly typed to be List<int>.<br \/>\nOn a related note, as you learned in Chapter 6, System.Object is the topmost parent class in the .NET Core Framework and can represent anything at all. If you declare a variable of type object, you have a strongly typed piece of data; however, what it points to in memory can differ based on your assignment of<\/p>\n<p>the reference. To gain access to the members the object reference is pointing to in memory, you need to perform an explicit cast.<br \/>\nAssume you have a simple class named Person that defines two automatic properties (FirstName and<br \/>\nLastName) both encapsulating a string:<br \/>\n\/\/Person.cs<br \/>\nnamespace DynamicKeyword; class Person<br \/>\n{<br \/>\npublic string FirstName { get; set; } = &quot;&quot;; public string LastName { get; set; } = &quot;&quot;;<br \/>\n}<\/p>\n<p>Now, observe the following code:<\/p>\n<p>static void UseObjectVariable()<br \/>\n{<br \/>\n\/\/ Create a new instance of the Person class<br \/>\n\/\/ and assign it to a variable of type System.Object<br \/>\nobject o = new Person() { FirstName = &quot;Mike&quot;, LastName = &quot;Larson&quot; };<\/p>\n<p>\/\/ Must cast object as Person to gain access<br \/>\n\/\/ to the Person properties.<br \/>\nConsole.WriteLine(&quot;Person's first name is {0}&quot;, ((Person)o).FirstName);<br \/>\n}<\/p>\n<p>Now, back to the dynamic keyword. From a high level, you can consider the dynamic keyword a specialized form of System.Object, in that any value can be assigned to a dynamic data type. At first glance, this can appear horribly confusing, as it appears you now have three ways to define data whose underlying type is not directly indicated in your code base. For example, this method<\/p>\n<p>static void PrintThreeStrings()<br \/>\n{<br \/>\nvar s1 = &quot;Greetings&quot;; object s2 = &quot;From&quot;;<br \/>\ndynamic s3 = &quot;Minneapolis&quot;;<\/p>\n<p>Console.WriteLine(&quot;s1 is of type: {0}&quot;, s1.GetType()); Console.WriteLine(&quot;s2 is of type: {0}&quot;, s2.GetType()); Console.WriteLine(&quot;s3 is of type: {0}&quot;, s3.GetType());<br \/>\n}<\/p>\n<p>would print out the following if invoked from your top-level statements:<\/p>\n<p>s1 is of type: System.String s2 is of type: System.String s3 is of type: System.String<\/p>\n<p>What makes a dynamic variable vastly different from a variable declared implicitly or via a System.<br \/>\nObject reference is that it is not strongly typed. Said another way, dynamic data is not statically typed. As far as the C# compiler is concerned, a data point declared with the dynamic keyword can be assigned any initial<\/p>\n<p>value at all and can be reassigned to any new (and possibly unrelated) value during its lifetime. Consider the following method and the resulting output:<\/p>\n<p>static void ChangeDynamicDataType()<br \/>\n{<br \/>\n\/\/ Declare a single dynamic data point<br \/>\n\/\/ named &quot;t&quot;.<br \/>\ndynamic t = &quot;Hello!&quot;;<br \/>\nConsole.WriteLine(&quot;t is of type: {0}&quot;, t.GetType());<\/p>\n<p>t = false;<br \/>\nConsole.WriteLine(&quot;t is of type: {0}&quot;, t.GetType());<\/p>\n<p>t = new List<int>();<br \/>\nConsole.WriteLine(&quot;t is of type: {0}&quot;, t.GetType());<br \/>\n}<\/p>\n<p>t is of type: System.String t is of type: System.Boolean<br \/>\nt is of type: System.Collections.Generic.List`1[System.Int32]<\/p>\n<p>At this point in your investigation, do be aware that the previous code would compile and execute identically if you were to declare the t variable as a System.Object. However, as you will soon see, the dynamic keyword offers many additional features.<\/p>\n<p>Calling Members on Dynamically Declared Data<br \/>\nGiven that a dynamic variable can take on the identity of any type on the fly (just like a variable of type System.Object), the next question on your mind might be about calling members on the dynamic variable (properties, methods, indexers, register with events, etc.). Syntactically speaking, it will look no different. Just apply the dot operator to the dynamic data variable, specify a public member, and supply any arguments (if required).<br \/>\nHowever (and this is a very big \u201chowever\u201d), the validity of the members you specify will not be checked by the compiler! Remember, unlike a variable defined as a System.Object, dynamic data is not statically typed. It is not until runtime that you will know whether the dynamic data you invoked supports a specified member, whether you passed in the correct parameters, whether you spelled the member correctly, and so on. Thus, as strange as it might seem, the following method compiles perfectly:<\/p>\n<p>static void InvokeMembersOnDynamicData()<br \/>\n{<br \/>\ndynamic textData1 = &quot;Hello&quot;; Console.WriteLine(textData1.ToUpper());<\/p>\n<p>\/\/ You would expect compiler errors here!<br \/>\n\/\/ But they compile just fine. Console.WriteLine(textData1.toupper()); Console.WriteLine(textData1.Foo(10, &quot;ee&quot;, DateTime.Now));<br \/>\n}<\/p>\n<p>Notice the second call to WriteLine() attempts to call a method named toupper() on the dynamic data point (note the incorrect casing\u2014it should be ToUpper()). As you can see, textData1 is of type string, and therefore, you know it does not have a method of this name in all lowercase letters. Furthermore, string certainly does not have a method named Foo() that takes int, string, and DateTime objects!<br \/>\nNevertheless, the C# compiler is satisfied. However, if you invoke this method from within Main(), you will get runtime errors like the following output:<\/p>\n<p>Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'string' does not contain a definition for 'toupper'<\/p>\n<p>Another obvious distinction between calling members on dynamic data and strongly typed data is that when you apply the dot operator to a piece of dynamic data, you will not see the expected Visual<br \/>\nStudio IntelliSense. The IDE will allow you to enter any member name you could dream up. This means you need to be extremely careful when you are typing C# code on such data points. Any misspelling<br \/>\nor incorrect capitalization of a member will throw a runtime error, specifically an instance of the<br \/>\nRuntimeBinderException class.<br \/>\nThe RuntimeBinderException represents an error that will be thrown if you attempt to invoke a member on a dynamic data type that does not actually exist (as in the case of the toupper() and Foo() methods). This same error will be raised if you specify the wrong parameter data to a member that does exist.<br \/>\nBecause dynamic data is so volatile, whenever you are invoking members on a variable declared with the C# dynamic keyword, you could wrap the calls within a proper try\/catch block and handle the error in a graceful manner, like so:<\/p>\n<p>static void InvokeMembersOnDynamicData()<br \/>\n{<br \/>\ndynamic textData1 = &quot;Hello&quot;;<\/p>\n<p>try<br \/>\n{<br \/>\nConsole.WriteLine(textData1.ToUpper()); Console.WriteLine(textData1.toupper()); Console.WriteLine(textData1.Foo(10, &quot;ee&quot;, DateTime.Now));<br \/>\n}<br \/>\ncatch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException ex)<br \/>\n{<br \/>\nConsole.WriteLine(ex.Message);<br \/>\n}<br \/>\n}<\/p>\n<p>If you call this method again, you will find the call to ToUpper() (note the capital T and U) works correctly; however, you then find the error data displayed to the console.<\/p>\n<p>HELLO<br \/>\n'string' does not contain a definition for 'toupper'<\/p>\n<p>Of course, the process of wrapping all dynamic method invocations in a try\/catch block is rather tedious. If you watch your spelling and parameter passing, this is not required. However, catching exceptions is handy when you might not know in advance if a member will be present on the target type.<\/p>\n<p>The Scope of the dynamic Keyword<br \/>\nRecall that implicitly typed data (declared with the var keyword) is possible only for local variables in a member scope. The var keyword can never be used as a return value, a parameter, or a member of a class\/ structure. This is not the case with the dynamic keyword, however. Consider the following class definition:<\/p>\n<p>namespace DynamicKeyword; class VeryDynamicClass<br \/>\n{<br \/>\n\/\/ A dynamic field.<br \/>\nprivate static dynamic _myDynamicField;<\/p>\n<p>\/\/ A dynamic property.<br \/>\npublic dynamic DynamicProperty { get; set; }<\/p>\n<p>\/\/ A dynamic return type and a dynamic parameter type.<br \/>\npublic dynamic DynamicMethod(dynamic dynamicParam)<br \/>\n{<br \/>\n\/\/ A dynamic local variable.<br \/>\ndynamic dynamicLocalVar = &quot;Local variable&quot;; int myInt = 10;<br \/>\nif (dynamicParam is int)<br \/>\n{<br \/>\nreturn dynamicLocalVar;<br \/>\n}<br \/>\nelse<br \/>\n{<br \/>\nreturn myInt;<br \/>\n}<br \/>\n}<br \/>\n}<\/p>\n<p>You could now invoke the public members as expected; however, as you are operating on dynamic methods and properties, you cannot be completely sure what the data type will be! While the<br \/>\nVeryDynamicClass definition might not be useful in a real-world application, it does illustrate the scope of where you can apply this C# keyword.<\/p>\n<p>Limitations of the dynamic Keyword<br \/>\nWhile a great many things can be defined using the dynamic keyword, there are some limitations regarding its usage. While they are not showstoppers, do know that a dynamic data item cannot make use of lambda expressions or C# anonymous methods when calling a method. For example, the following code will always result in errors, even if the target method does indeed take a delegate parameter that takes a string value and returns void:<\/p>\n<p>dynamic a = GetDynamicObject();<\/p>\n<p>\/\/ Error! Methods on dynamic data can't use lambdas! a.Method(arg =&gt; Console.WriteLine(arg));<\/p>\n<p>To circumvent this restriction, you will need to work with the underlying delegate directly, using the techniques described in Chapter 12. Another limitation is that a dynamic point of data cannot understand any extension methods (see Chapter 11). Unfortunately, this would also include any of the extension methods that come from the LINQ APIs. Therefore, a variable declared with the dynamic keyword has limited use within LINQ to Objects and other LINQ technologies.<\/p>\n<p>dynamic a = GetDynamicObject();<br \/>\n\/\/ Error! Dynamic data can't find the Select() extension method! var data = from d in a select d;<\/p>\n<p>Practical Uses of the dynamic Keyword<br \/>\nGiven that dynamic data is not strongly typed, not checked at compile time, has no ability to trigger IntelliSense, and cannot be the target of a LINQ query, you are absolutely correct to assume that using the dynamic keyword just for the sake of doing so is a poor programming practice.<br \/>\nHowever, in a few circumstances, the dynamic keyword can radically reduce the amount of code you<br \/>\nneed to author by hand. Specifically, if you are building a .NET application that makes heavy use of late binding (via reflection), the dynamic keyword can save you typing time. As well, if you are building a .NET application that needs to communicate with legacy COM libraries (such as Microsoft Office products), you can greatly simplify your code base via the dynamic keyword. By way of a final example, web applications built using ASP.NET Core frequently use the ViewBag type, which can also be accessed in a simplified manner using the dynamic keyword.<\/p>\n<p>\u25a0Note Com interaction is strictly a Windows paradigm and removes the cross-platform capabilities of your application.<\/p>\n<p>Like any \u201cshortcut,\u201d you need to weigh the pros and cons. The use of the dynamic keyword is a trade- off between brevity of code and type safety. While C# is a strongly typed language at its core, you can opt in (or opt out) of dynamic behaviors on a call-by-call basis. Always remember that you never need to use the dynamic keyword. You could always get to the same end result by authoring alternative code by hand (and typically much more of it).<\/p>\n<p>The Role of the Dynamic Language Runtime<br \/>\nNow that you better understand what \u201cdynamic data\u201d is about, let\u2019s learn how it is processed. Since the release of .NET 4.0, the Common Language Runtime (CLR) was supplemented with a complementary runtime environment named the Dynamic Language Runtime. The concept of a \u201cdynamic runtime\u201d is certainly not new. In fact, many programming languages such as JavaScript, LISP, Ruby, and Python have used it for years. In a nutshell, a dynamic runtime allows a dynamic language the ability to discover types completely at runtime with no compile-time checks.<\/p>\n<p>\u25a0Note  While a lot of the Dlr was ported to .NET Core (starting with 3.0), feature parity between the Dlr in<br \/>\n.NET 6 and .NET 4.8 hasn\u2019t been achieved.<\/p>\n<p>If you have a background in strongly typed languages (including C#, without dynamic types), the notion of such a runtime might seem undesirable. After all, you typically want to receive compile-time errors, not<\/p>\n<p>runtime errors, wherever possible. Nevertheless, dynamic languages\/runtimes do provide some interesting features, including the following:<br \/>\n\u2022An extremely flexible code base. You can refactor code without making numerous changes to data types.<br \/>\n\u2022A simple way to interoperate with diverse object types built in different platforms and programming languages.<br \/>\n\u2022A way to add or remove members to a type, in memory, at runtime.<br \/>\nOne role of the DLR is to enable various dynamic languages to run with the .NET Runtime and give them a way to interoperate with other .NET code. These languages live in a dynamic universe, where type is discovered solely at runtime. And yet, these languages have access to the richness of the .NET base class<br \/>\nlibraries. Even better, their code bases can interoperate with C# (or vice versa), thanks to the inclusion of the<br \/>\ndynamic keyword.<\/p>\n<p>\u25a0 Note  This chapter will not address how the Dlr can be used to integrate with dynamic languages.<\/p>\n<p>The Role of Expression Trees<br \/>\nThe DLR makes use of expression trees to capture the meaning of a dynamic call in neutral terms. For example, take the following C# code:<\/p>\n<p>dynamic d = GetSomeData(); d.SuperMethod(12);<\/p>\n<p>In this example, the DLR will automatically build an expression tree that says, in effect, \u201cCall the method named SuperMethod on object d, passing in the number 12 as an argument.\u201d This information (formally termed the payload) is then passed to the correct runtime binder, which again could be the C# dynamic binder or even (as explained shortly) legacy COM objects.<br \/>\nFrom here, the request is mapped into the required call structure for the target object. The nice thing about these expression trees (beyond that you do not need to manually create them) is that this allows you to write a fixed C# code statement and not worry about what the underlying target actually is.<\/p>\n<p>Dynamic Runtime Lookup of Expression Trees<br \/>\nAs explained, the DLR will pass the expression trees to a target object; however, this dispatching will be influenced by a few factors. If the dynamic data type is pointing in memory to a COM object, the expression tree is sent to a low-level COM interface named IDispatch. As you might know, this interface was COM\u2019s way of incorporating its own set of dynamic services. COM objects, however, can be used in a .NET application without the use of the DLR or C# dynamic keyword. Doing so, however (as you will see), tends to result in much more complex C# coding.<br \/>\nIf the dynamic data is not pointing to a COM object, the expression tree may be passed to an object implementing the IDynamicObject interface. This interface is used behind the scenes to allow a language, such as IronRuby, to take a DLR expression tree and map it to Ruby specifics.<br \/>\nFinally, if the dynamic data is pointing to an object that is not a COM object and does not implement IDynamicObject, the object is a normal, everyday .NET object. In this case, the expression tree is dispatched to the C# runtime binder for processing. The process of mapping the expression tree to .NET specifics involves reflection services.<\/p>\n<p>After the expression tree has been processed by a given binder, the dynamic data will be resolved to the real in-memory data type, after which the correct method is called with any necessary parameters. Now, let\u2019s look at a few practical uses of the DLR, beginning with the simplification of late-bound .NET calls.<\/p>\n<p>Simplifying Late-Bound Calls Using Dynamic Types<br \/>\nOne instance where you might decide to use the dynamic keyword is when you are working with reflection services, specifically when making late-bound method calls. Earlier in this chapter, you saw a few examples of when this type of method call can be useful, most commonly when you are building some type of extensible application. At that time, you learned how to use the Activator.CreateInstance() method to create an object, for which you have no compile-time knowledge of (beyond its display name). You can then make use of the types of the System.Reflection namespace to invoke members via late binding. Recall the earlier example:<\/p>\n<p>static void CreateUsingLateBinding(Assembly asm)<br \/>\n{<br \/>\ntry<br \/>\n{<br \/>\n\/\/ Get metadata for the Minivan type.<br \/>\nType miniVan = asm.GetType(&quot;CarLibrary.MiniVan&quot;);<\/p>\n<p>\/\/ Create the Minivan on the fly.<br \/>\nobject obj = Activator.CreateInstance(miniVan);<\/p>\n<p>\/\/ Get info for TurboBoost.<br \/>\nMethodInfo mi = miniVan.GetMethod(&quot;TurboBoost&quot;);<\/p>\n<p>\/\/ Invoke method (&quot;null&quot; for no parameters). mi.Invoke(obj, null);<br \/>\n}<br \/>\ncatch (Exception ex)<br \/>\n{<br \/>\nConsole.WriteLine(ex.Message);<br \/>\n}<br \/>\n}<\/p>\n<p>While this code works as expected, you might agree it is a bit clunky. You must manually make use of the MethodInfo class, manually query the metadata, and so forth. The following is a version of this same method, now using the C# dynamic keyword and the DLR:<\/p>\n<p>static void InvokeMethodWithDynamicKeyword(Assembly asm)<br \/>\n{<br \/>\ntry<br \/>\n{<br \/>\n\/\/ Get metadata for the Minivan type.<br \/>\nType miniVan = asm.GetType(&quot;CarLibrary.MiniVan&quot;);<\/p>\n<p>\/\/ Create the Minivan on the fly and call method! dynamic obj = Activator.CreateInstance(miniVan); obj.TurboBoost();<br \/>\n}<\/p>\n<p>catch (Exception ex)<br \/>\n{<br \/>\nConsole.WriteLine(ex.Message);<br \/>\n}<br \/>\n}<\/p>\n<p>By declaring the obj variable using the dynamic keyword, the heavy lifting of reflection is done on your behalf, courtesy of the DRL.<\/p>\n<p>Leveraging the dynamic Keyword to Pass Arguments<br \/>\nThe usefulness of the DLR becomes even more obvious when you need to make late-bound calls on methods that take parameters. When you use \u201clonghand\u201d reflection calls, arguments need to be packaged up as an array of objects, which are passed to the Invoke() method of MethodInfo.<br \/>\nTo illustrate using a fresh example, begin by creating a new C# Console Application project named LateBindingWithDynamic. Next, add a Class Library project named MathLibrary. Rename the initial Class1.cs file of the MathLibrary project to SimpleMath.cs, and implement the class like so:<\/p>\n<p>namespace MathLibrary; public class SimpleMath<br \/>\n{<br \/>\npublic int Add(int x, int y)<br \/>\n{<br \/>\nreturn x + y;<br \/>\n}<br \/>\n}<\/p>\n<p>If you are using Visual Studio, update the MathLibrary.csproj file with the following (to copy the compiled assembly to the LateBindingWithDynamic target directory):<\/p>\n<p><Target Name=\"PostBuild\" AfterTargets=\"PostBuildEvent\"><br \/>\n<Exec Command=\"copy $(TargetPath) $(SolutionDir)LateBindingWithDynamic\\$(OutDir)$(Target FileName) \/Y\" \/><br \/>\n<\/Target><\/p>\n<p>If you are using Visual Studio Code, update the MathLibrary.csproj file with the following (to copy the compiled assembly to the LateBindingWithDynamic project directory):<\/p>\n<p><Target Name=\"PostBuild\" AfterTargets=\"PostBuildEvent\"><br \/>\n<Exec Command=\"copy $(TargetPath) $(ProjectDir)..\\LateBindingWithDynamic\\$(TargetFile Name) \/Y\" \/><br \/>\n<\/Target><\/p>\n<p>Now, back in the LateBindingWithDynamic project and the Program.cs file, update the using<br \/>\nstatements to the following:<\/p>\n<p>using System.Reflection;<br \/>\nusing Microsoft.CSharp.RuntimeBinder;<\/p>\n<p>Next, add the following method to the Program.cs file, which invokes the Add() method using typical reflection API calls:<\/p>\n<p>static void AddWithReflection()<br \/>\n{<br \/>\nAssembly asm = Assembly.LoadFrom(&quot;MathLibrary&quot;); try<br \/>\n{<br \/>\n\/\/ Get metadata for the SimpleMath type.<br \/>\nType math = asm.GetType(&quot;MathLibrary.SimpleMath&quot;);<\/p>\n<p>\/\/ Create a SimpleMath on the fly.<br \/>\nobject obj = Activator.CreateInstance(math);<\/p>\n<p>\/\/ Get info for Add.<br \/>\nMethodInfo mi = math.GetMethod(&quot;Add&quot;);<\/p>\n<p>\/\/ Invoke method (with parameters).<br \/>\nobject[] args = { 10, 70 };<br \/>\nConsole.WriteLine(&quot;Result is: {0}&quot;, mi.Invoke(obj, args));<br \/>\n}<br \/>\ncatch (Exception ex)<br \/>\n{<br \/>\nConsole.WriteLine(ex.Message);<br \/>\n}<br \/>\n}<\/p>\n<p>Now, consider the simplification of the previous logic with the dynamic keyword, via the following new method:<\/p>\n<p>static void AddWithDynamic()<br \/>\n{<br \/>\nAssembly asm = Assembly.LoadFrom(&quot;MathLibrary&quot;);<\/p>\n<p>try<br \/>\n{<br \/>\n\/\/ Get metadata for the SimpleMath type.<br \/>\nType math = asm.GetType(&quot;MathLibrary.SimpleMath&quot;);<\/p>\n<p>\/\/ Create a SimpleMath on the fly.<br \/>\ndynamic obj = Activator.CreateInstance(math);<\/p>\n<p>\/\/ Note how easily we can now call Add().<br \/>\nConsole.WriteLine(&quot;Result is: {0}&quot;, obj.Add(10, 70));<br \/>\n}<br \/>\ncatch (RuntimeBinderException ex)<br \/>\n{<br \/>\nConsole.WriteLine(ex.Message);<br \/>\n}<br \/>\n}<\/p>\n<p>Not too shabby! If you call both methods, you will see identical output. However, when using the dynamic keyword, you saved yourself quite a bit of work. With dynamically defined data, you no longer need to manually package up arguments as an array of objects, query the assembly metadata, or set other such details. If you are building an application that makes heavy use of dynamic loading\/late binding, I am sure you can see how these code savings would add up over time.<\/p>\n<p>Summary<br \/>\nReflection is an interesting aspect of a robust object-oriented environment. In the world of .NET, the keys to reflection services revolve around the System.Type class and the System.Reflection namespace. As you have seen, reflection is the process of placing a type under the magnifying glass at runtime to understand the who, what, where, when, why, and how of a given item.<br \/>\nLate binding is the process of creating an instance of a type and invoking its members without prior knowledge of the specific names of said members. Late binding is often a direct result of dynamic loading, which allows you to load a .NET assembly into memory programmatically. As shown during this chapter\u2019s extendable application example, this is a powerful technique used by tool builders as well as tool consumers.<br \/>\nThis chapter also examined the role of attribute-based programming. When you adorn your types with attributes, the result is the augmentation of the underlying assembly metadata.<br \/>\nThe dynamic keyword allows you to define data whose identity is not known until runtime. When processed by the Dynamic Language Runtime, the automatically created \u201cexpression tree\u201d will be passed to the correct dynamic language binder, where the payload will be unpackaged and sent to the correct object member.<br \/>\nUsing dynamic data and the DLR, complex C# programming tasks can be radically simplified, especially the act of incorporating COM libraries into your .NET applications.<br \/>\nWhile these features can certainly simplify your code, always remember that dynamic data makes your C# code much less type-safe and open to runtime errors. Be sure you weigh the pros and cons of using dynamic data in your C# projects, and test accordingly!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>CHAPTER 17 Type Reflection, Late Binding, Attribute, and Dynamic Types As shown in Chapter 16, assemblies are the basic unit of deployment in the .NET universe. Using the integrated Object Browser of Visual Studio (and numerous other IDEs), you can examine the types within a project\u2019s referenced set of assemblies. Furthermore, external tools such as [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[22],"class_list":["post-332","post","type-post","status-publish","format-standard","hentry","category-csharp","tag-pro-csharp10-with-net6"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/332","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=332"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/332\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=332"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=332"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=332"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}