Use of Attributes in .NET using C#

Use of Attributes in .NET using C#

Attributes are familiar to developers who use C++ to write COM components (through their use in Microsoft’s COM Interface Definition Language [IDL]).​​ The initial idea of an attribute was that it provided extra information concerning some item in the program that could be used by the compiler.

Attributes are supported in .NET — and now by C++, C#, and Visual Basic 2012. What is, however, particularly innovative about attributes in .NET is that you can define your own custom attributes in your source code. These user-defined attributes will be placed with the metadata for the corresponding data types or methods. This can be useful for documentation purposes, in which they can be used with reflection technology to perform programming tasks based on attributes. In addition, in common with the .NET philosophy of language independence, attributes can be defined in source code in one language and read by code written in another language.

 

CUSTOM ATTRIBUTES

You have already seen how you can define attributes on various items within your program. These attributes have been defined by Microsoft as part of the .NET Framework class library, and many of them receive special support from the C# compiler. This means that for those particular attributes, the compiler can customize the compilation process in specific ways — for example, laying out a struct in memory according to the details in the StructLayout attributes.

The .NET Framework also enables you to define your own attributes. Obviously, these attributes won’t have any effect on the compilation process because the compiler has no intrinsic awareness of them. However, these attributes will be emitted as metadata in the compiled assembly when they are applied to program elements. By itself, this metadata might be useful for documentation purposes, but what makes attributes really​​ powerful is that by using reflection, your code can read this metadata and use it to make decisions at runtime. This means that the custom attributes that you define can directly affect how your code runs.

For example, custom attributes can be used to enable declarative code access security checks for custom permission classes, to associate​​ information with program elements that can then be used by testing tools, or when developing extensible frameworks that allow the loading of plug-ins or modules.

 

Writing Custom Attributes

To understand how to write your own custom attributes, it is useful​​ to know what the compiler does when it encounters an element in your code that has a custom attribute applied to it. To take the database example,​​ suppose that you have a C# property declaration that looks like this:

[FieldName("IdentificationNumber")]

public string IdentificationNumber

{

get {

// etc.

 

When the C# compiler recognizes that this property has an attribute applied to it (FieldName), it first appends the string Attribute to this name, forming the combined name FieldNameAttribute. The compiler​​ then searches all the namespaces in its search path (those namespaces that have been mentioned in a using statement) for a class with the specified name. Note that if you mark an item with an attribute whose name​​ already ends in the string Attribute, the compiler will not add the string to the name a second time; it will leave the attribute name unchanged. Therefore, the preceding code is equivalent to this:

[FieldNameAttribute("IdentificationNumber")]

public string IdentificationNumber

{

get {

// etc.

The​​ compiler expects to find a class with this name, and it expects this class to be derived directly or indirectly from System.Attribute. The compiler also expects that this class contains information governing the use of the attribute. In particular, the attribute class needs to specify the following:

​​ The types of program elements to which the attribute can be applied (classes, structs, properties, methods, and so on)

​​ Whether it is legal for the attribute to be applied more than once to the same program element.

​​ Whether the attribute, when applied to a class or interface, is inherited by derived classes and interfaces.

​​ The mandatory and optional parameters the attribute takes If the compiler cannot find a corresponding attribute class, or if it finds one but the way that you have used that attribute does not match the information in the attribute class, the compiler will raise a compilation error. For example, if the attribute class indicates that the attribute can be applied only to classes but you have applied it to a struct definition, a compilation error will occur. Continuing with the example, assume that you have defined the FieldName attribute like this:

[AttributeUsage(AttributeTargets.Property,AllowMultiple=false,Inherited=false)]

public class FieldNameAttribute: Attribute

{

private string name;

public FieldNameAttribute(string name)

{

this.name = name;

}

}

The following sections discuss each element of this definition.

 

AttributeUsage Attribute

The first thing to note is that the attribute class​​ itself is marked with an attribute — the System.AttributeUsage attribute. This is an attribute defined by Microsoft for which the C# compiler provides special support. (You could argue that AttributeUsage isn’t an attribute at all; it is more like a meta-attribute, because it applies only to other attributes, not simply to any class.) The primary purpose of AttributeUsage is to identify the types of program elements to which your custom attribute can be applied. This information is provided by the first parameter of the AttributeUsage attribute. This parameter is mandatory, and it is of an enumerated type, AttributeTargets. In the previous example, you have indicated that the FieldName attribute can be applied only to properties, which is fi ne, because that is exactly what you have applied it to in the earlier code fragment. The members of the AttributeTargets

enumeration are as follows:

​​ All

​​ Assembly

​​ Class

​​ Constructor

​​ Delegate

​​ Enum

​​ Event

​​ Field

​​ GenericParameter (.NET 2.0 and higher only)

​​ Interface

​​ Method

​​ Module

​​ Parameter

​​ Property

​​ ReturnValue

​​ Struct

 

This list identifies all the program elements to which you can apply attributes. Note that when applying the attribute to a program element, you place the attribute in square​​ brackets immediately before the element.

However, two values in the preceding list do not correspond to any program element: Assembly and Module. An attribute can be applied to an assembly or a module as a whole, rather than to an element in your code; in​​ this case the attribute can be placed anywhere in your source code, but it must be prefixed with the Assembly or Module keyword:

 

[assembly:SomeAssemblyAttribute(Parameters)]

[module:SomeAssemblyAttribute(Parameters)]

When indicating the valid target elements of a custom attribute, you can combine these values using the bitwise OR operator. For example, if you want to indicate that your FieldName attribute can be applied to both properties and fields, you would use the following:

 

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field,AllowMultiple=false,

Inherited=false)]

public class FieldNameAttribute: Attribute

 

You can also use AttributeTargets.All to indicate that your attribute can be applied to all types of program elements. The AttributeUsage attribute also contains two other parameters, AllowMultiple and Inherited. These are specifi ed using the syntax of <ParameterName>=<ParameterValue>, instead of simply specifying the values for these parameters. These parameters are optional — you can omit them.

The AllowMultiple parameter indicates whether an attribute can be applied more than once to the same item. The fact that it is set to false here indicates that the compiler should raise an error if it sees something

like this:

[FieldName("IdentificationNumber")]

[FieldName("InsuranceNumber")]

public string IdentificationNumber

{

// etc.

If the Inherited parameter is set to true, an attribute applied to a class or interface will also automatically be applied to all derived classes or interfaces.​​ If the attribute is applied to a method or property, it will automatically apply to any overrides of that method or property, and so on.

Specifying Attribute Parameters​​ 

This section demonstrates how you can specify the parameters that your custom​​ attribute takes. When the compiler encounters a statement such as the following, it examines the parameters passed into the attribute — which is a string — and looks for a constructor for the attribute that takes exactly those parameters:

[FieldName("IdentificationNumber")]

public string IdentificationNumber

{

// etc.

If the compiler finds an appropriate constructor, it emits the specified metadata to the assembly. If the compiler does not find an appropriate constructor, a compilation error occurs. As discussed, reflection involves reading metadata (attributes) from assemblies and instantiating the attribute classes they represent. Because of this, the compiler must ensure that an appropriate constructor exists that will

allow the runtime instantiation of the specified attribute.

 

In the example, you have supplied just one constructor for FieldNameAttribute, and this constructor takes one string parameter. Therefore, when applying the FieldName attribute to a property, you must supply one string as a parameter, as shown in the preceding code.

To allow a choice of what types of parameters should be supplied with an attribute, you can provide different constructor overloads, although normal practice is to supply just one constructor and use properties to define​​ any other optional parameters, as explained next.

Specifying Optional Attribute Parameters

As demonstrated with the AttributeUsage attribute, an alternative syntax enables optional parameters to be added to an attribute. This syntax involves specifying the names and values of the optional parameters.

It works through public properties or fields in the attribute class. For example, suppose that you modify the definition of the IdentificationNumber property as follows:

[FieldName("IdentificationNumber", Comment="This is the primary key field")]

public string IdentificationNumber

{

// etc.

In this case, the compiler recognizes the <ParameterName>=<ParameterValue> syntax of the second

parameter and does not attempt to match this parameter to a FieldNameAttribute constructor. Instead, it

looks for a public property or field (although public fields are not considered good programming practice,

so normally you will work with properties) of that name that it can use to set the value of this parameter. If

you want the previous code to work, you have to add some code to FieldNameAttribute:

[AttributeUsage(AttributeTargets.Property,AllowMultiple=false,Inherited=false)]

public class FieldNameAttribute: Attribute

{

private string comment;

public string Comment

{

get

{

return comment;

}

set

{

comment = value;

}

}

// etc

}

 

Custom Attribute Example: (A very good and practical example of Attribute):

Step1 :​​ 

This section starts with the core​​ CustomAttribute​​ assembly. The source code is contained in the file​​ CustomAttribute.cs, The syntax for this is given below :

 

using​​ System;

 

namespace​​ CustomNewAttribute

{

 ​​ ​​ ​​​​ [AttributeUsage(

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ AttributeTargets.Class |​​ AttributeTargets.Method,

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ AllowMultiple =​​ true, Inherited =​​ false)]

 ​​ ​​ ​​​​ public​​ class​​ LastChangedAttribute​​ :​​ Attribute

 ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ private​​ readonly​​ DateTime​​ _dateChanged;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ private​​ readonly​​ string​​ _changes;

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ LastChangedAttribute(string​​ dateChanged,​​ string​​ changes)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ _dateChanged =​​ DateTime.Parse(dateChanged);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ _changes = changes;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ DateTime​​ DateChanged

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ get​​ {​​ return​​ _dateChanged; }

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ string​​ Changes

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ get​​ {​​ return​​ _changes; }

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ string​​ Issues {​​ get;​​ set; }

 ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​​​ [AttributeUsage(AttributeTargets.Assembly)]

 ​​ ​​ ​​​​ public​​ class​​ SupportsNewCustomAttribute​​ :​​ Attribute

 ​​ ​​ ​​​​ {

 ​​ ​​ ​​​​ }

}

 

Build the project.

 

Step 2 : Create a second project with ​​ the Name VectorClass.

To use​​ these attributes, you will be using a VectorClass. Note that you need to reference the CustomNewAttribute library that you just created. You also need to indicate the corresponding namespace with a using statement so the compiler can recognize the attributes:​​ 

Project structures are given below:

Code for VectorClass.cs is:

using​​ CustomNewAttribute;

using​​ System;

using​​ System.Collections;

using​​ System.Text;

 

[assembly:​​ SupportsNewCustom]

 

namespace​​ VectorClass

{

 ​​ ​​ ​​​​ [LastChanged("08 Aug 2016",​​ "IEnumerable interface implemented "​​ +

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ "So Vector can now be treated as a collection")]

 ​​ ​​ ​​​​ [LastChanged("09 Sep 2016",​​ "IFormattable interface implemented "​​ +

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ "So Vector now responds to​​ format specifiers N and VE")]

 ​​ ​​ ​​​​ class​​ Vector​​ :​​ IFormattable,​​ IEnumerable

 ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ double​​ x, y, z;

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ Vector(double​​ x,​​ double​​ y,​​ double​​ z)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ this.x = x;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ this.y = y;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ this.z = z;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ [LastChanged("09 Sep 2016",​​ "Method added in order to provide formatting support")]

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ string​​ ToString(string​​ format,​​ IFormatProvider​​ formatProvider)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ if​​ (format ==​​ null)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ ToString();

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ string​​ formatUpper = format.ToUpper();

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ switch​​ (formatUpper)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ case​​ "N":

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ "|| "​​ + Norm().ToString() +​​ " ||";

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ case​​ "VE":

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ String.Format("(​​ {0:E}, {1:E}, {2:E} )", x, y, z);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ case​​ "IJK":

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ StringBuilder​​ sb =​​ new​​ StringBuilder(x.ToString(), 30);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ sb.Append(" i + ");

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ sb.Append(y.ToString());

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ sb.Append("​​ j + ");

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ sb.Append(z.ToString());

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ sb.Append(" k");

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ sb.ToString();

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ default:

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ ToString();

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ Vector(Vector​​ rhs)

​​  ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ x = rhs.x;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ y = rhs.y;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ z = rhs.z;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ [LastChanged("09 Sep 2016",​​ "Method added in order to provide collection support")]

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ IEnumerator​​ GetEnumerator()

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ new​​ VectorEnumerator(this);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ override​​ string​​ ToString()

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ "( "​​ + x +​​ " , "​​ + y +​​ " , "​​ + z +​​ " )";

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ double​​ this[uint​​ i]

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ get

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ switch​​ (i)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ case​​ 0:

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ x;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ case​​ 1:

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ y;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ case​​ 2:

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ z;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ default:

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​  ​​ ​​ ​​ ​​ ​​​​ throw​​ new​​ IndexOutOfRangeException(

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ "Attempt to retrieve Vector element"​​ + i);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ set

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ switch​​ (i)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ case​​ 0:

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ x =​​ value;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ break;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ case​​ 1:

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ y =​​ value;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ break;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ case​​ 2:

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ z =​​ value;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ break;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ default:

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ throw​​ new​​ IndexOutOfRangeException(

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ "Attempt to set Vector element"​​ + i);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ static​​ bool​​ operator​​ ==(Vector​​ lhs,​​ Vector​​ rhs)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ if​​ (System.Math.Abs(lhs.x - rhs.x) <​​ double.Epsilon &&

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ System.Math.Abs(lhs.y - rhs.y) <​​ double.Epsilon &&

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ System.Math.Abs(lhs.z - rhs.z) <​​ double.Epsilon)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ true;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ else

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ false;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ static​​ bool​​ operator​​ !=(Vector​​ lhs,​​ Vector​​ rhs)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ !(lhs == rhs);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ static​​ Vector​​ operator​​ +(Vector​​ lhs,​​ Vector​​ rhs)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ Vector​​ result =​​ new​​ Vector(lhs);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ result.x += rhs.x;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ result.y += rhs.y;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ result.z += rhs.z;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ result;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ static​​ Vector​​ operator​​ *(double​​ lhs,​​ Vector​​ rhs)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ new​​ Vector(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ static​​ Vector​​ operator​​ *(Vector​​ lhs,​​ double​​ rhs)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ rhs * lhs;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ static​​ double​​ operator​​ *(Vector​​ lhs,​​ Vector​​ rhs)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ lhs.x * rhs.x + lhs.y + rhs.y + lhs.z * rhs.z;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ double​​ Norm()

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ x * x + y * y + z * z;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ #region​​ enumerator class

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ [LastChanged("09 Sep 2016",​​ "Class created as​​ part of collection support for Vector")]

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ private​​ class​​ VectorEnumerator​​ :​​ IEnumerator

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ readonly​​ Vector​​ _theVector;  ​​ ​​ ​​ ​​​​ // Vector object that this enumerato refers to​​ 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ int​​ _location;  ​​​​ // which element of _theVector​​ the enumerator is currently referring to​​ 

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ VectorEnumerator(Vector​​ theVector)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ _theVector = theVector;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ _location = -1;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ bool​​ MoveNext()

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ ++_location;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ (_location > 2) ?​​ false​​ :​​ true;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ object​​ Current

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ get

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ if​​ (_location < 0 || _location > 2)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​  ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ throw​​ new​​ InvalidOperationException(

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ "The enumerator is either before the first element or "​​ +

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ "after the last element of the Vector");

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return​​ _theVector[(uint)_location];

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ public​​ void​​ Reset()

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ _location = -1;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ #endregion

 ​​ ​​ ​​​​ }

}

 

Step 3:​​ USING REFLECTION

In this section, you take a closer look at the System.Type class,​​ which enables you to access information concerning the definition of any data type. You’ll also look at the System.Reflection.Assembly class, which you can use to access information about an assembly or to load that assembly into your program. Finally, you​​ will combine the code in this section with the code in the previous section to complete the CustomNewAttribute.

Create a new project with name : LookUpCustomAttribute and project type Windows Apllication.

Code for LookUpCustomeAttribute are :​​ 

using​​ CustomNewAttribute;

using​​ System;

using​​ System.Collections.Generic;

using​​ System.Linq;

using​​ System.Reflection;

using​​ System.Text;

using​​ System.Threading.Tasks;

using​​ System.Windows.Forms;

 

namespace​​ LookUpCustomAttribute

{

 ​​ ​​ ​​​​ internal​​ class​​ WhatsNewChecker

 ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ private​​ static​​ readonly​​ StringBuilder​​ outputText =​​ new​​ StringBuilder(1000);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ private​​ static​​ DateTime​​ backDateTo =​​ new​​ DateTime(2016, 08, 08);

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ private​​ static​​ void​​ Main()

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ Assembly​​ theAssembly =​​ Assembly.Load("VectorClass");

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ Attribute​​ supportsAttribute =​​ 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ Attribute.GetCustomAttribute(

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ theAssembly,​​ typeof(SupportsNewCustomAttribute));

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ string​​ name = theAssembly.FullName;

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ AddToMessage("Assembly: "​​ + name);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ if​​ (supportsAttribute ==​​ null)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ AddToMessage(

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ "This assembly does not support New Custom attributes");

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ else

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ AddToMessage("Defined Types:");

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ Type[] types = theAssembly.GetTypes();

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ foreach​​ (Type​​ definedType​​ in​​ types)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ DisplayTypeInfo(definedType);

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ MessageBox.Show(outputText.ToString(),

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ "What\'s New since "​​ + backDateTo.ToLongDateString());

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ Console.ReadLine();

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ private​​ static​​ void​​ DisplayTypeInfo(Type​​ type)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ // make sure we only​​ pick out classes

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ if​​ (!(type.IsClass))

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ AddToMessage("\nclass "​​ + type.Name);

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ Attribute[] attribs =​​ Attribute.GetCustomAttributes(type);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ if​​ (attribs.Length == 0)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ AddToMessage("No changes to this class");

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ else

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ foreach​​ (Attribute​​ attrib​​ in​​ attribs)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ WriteAttributeInfo(attrib);

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ MethodInfo[] methods = type.GetMethods();

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ AddToMessage("CHANGES TO METHODS OF THIS CLASS:");

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ foreach​​ (MethodInfo​​ nextMethod​​ in​​ methods)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ object[] attribs2 =

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ nextMethod.GetCustomAttributes(

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ typeof(LastChangedAttribute),​​ false);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ if​​ (attribs2 !=​​ null)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ AddToMessage(

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ nextMethod.ReturnType +​​ " "​​ + nextMethod.Name +​​ "()");

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ foreach​​ (Attribute​​ nextAttrib​​ in​​ attribs2)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ WriteAttributeInfo(nextAttrib);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ private​​ static​​ void​​ WriteAttributeInfo(Attribute​​ attrib)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ LastChangedAttribute​​ lastChangedAttrib =

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ attrib​​ as​​ LastChangedAttribute;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ if​​ (lastChangedAttrib ==​​ null)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return;

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ // check that date is in range

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ DateTime​​ ChangedDate = lastChangedAttrib.DateChanged;

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ if​​ (ChangedDate < backDateTo)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ return;

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ AddToMessage(" ​​ Changed: "​​ +

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ ChangedDate.ToLongDateString() +​​ ":");

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ AddToMessage("  ​​ ​​​​ "​​ + lastChangedAttrib.Changes);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ if​​ (lastChangedAttrib.Issues !=​​ null)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ AddToMessage("  ​​ ​​​​ Outstanding​​ issues:"​​ +

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ lastChangedAttrib.Issues);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ private​​ static​​ void​​ AddToMessage(string​​ message)

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ {

 ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ outputText.Append("\n"​​ + message);

 ​​ ​​ ​​ ​​ ​​ ​​ ​​​​ }

 ​​ ​​ ​​​​ }

}

 

Output :​​ 

 


Recommended Book for MVC : List of Some Important books for .NET Framework, C#, MVC, WCF



(Visited 199 times, 1 visits today)

Leave a Reply

Your email address will not be published. Required fields are marked *