# 九、深入和更多要知道的事情

为了保持主题简洁，我放弃了可能演变成更深入讨论的特性。本章是关于这些特性中的一些，如果只是为了强调它们是 C# 语言的一部分，并且您可能会经常遇到它们。

## 用属性装饰代码

属性是 C# 的一个特性，它允许你用各种工具的元信息来修饰代码。我使用“工具”这个术语并不严格，但它可以是 C# 编译器、测试框架或用户界面技术。本质上，这些工具读取属性来决定如何使用您的代码。我将向您展示几个示例，以便您在代码中遇到属性语法时能够熟悉它。

`Obsolete`属性允许您指示某些代码已被否决。这是一个 C# 编译器属性，编译器会发出一条关于它的使用的消息。下面的代码显示了一个示例。

```cs
    using System;

    public class ShoppingCart
    {
        [Obsolete("Method planned for deprecation on date – use ... instead.")]
        public void Add(string item) { }

        [Obsolete("Method is obsolete and can no longer be used", error: true)]
        public decimal CalculateTax(decimal[] prices) { return 0; }
    }

```

代码清单 117

当 C# 编译器看到修饰`Add`的`Obsolete`属性时，它会显示一个警告，消息参数与`Obsolete`属性的参数相匹配。在第二个示例中，编译器将消息显示为错误，您将无法编译，因为第二个参数`true`指示编译器应该将该方法的使用视为错误。

下一个例子使用微软的单元测试软件 MSTest 进行单元测试。

```cs
    using System;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    namespace UnitTestProject1
    {
        [TestClass]
        public class UnitTest1
        {
            [TestMethod]
            public void TestMethod1()
            {
            }
        }
    }

```

代码清单 118

和大多数单元测试框架一样，MSTest 有一个测试运行器，它加载单元测试代码，寻找用`TestClass`属性修饰的类，并执行用`TestMethod`属性修饰的方法。它通过 C# 的另一个功能——反射来实现这一点。

## 使用反射

反射使您能够检查编译的。NET 代码。通过反射，您可以构建有用的工具，如 MSTest，动态实例化类型并执行它们的代码，等等。下面的代码使用反射动态实例化一个类型并执行它的一个方法。

```cs
    using System.Linq;
    using System;
    using System.Reflection;

    public class FinancialCalculator
    {
        public decimal Sum(decimal[] numbers)
        {
            return numbers.Sum();
        }
    }

    public class Program
    {
        public static void Main()
        {
            decimal[] prices = { 1m, 2m, 3m };

            Type calcType = typeof(FinancialCalculator);
            MethodInfo sumMethod = calcType.GetMethod("Sum");
            FinancialCalculator calc =
                (FinancialCalculator)Activator.CreateInstance(calcType);
            decimal sum = (decimal)sumMethod.Invoke(calc, new object[] { prices });

            Console.WriteLine($"Sum: {sum}\nPress any key to continue.");
            Console.ReadKey();
        }
    }

```

代码清单 119

`FinancialCalculator.Sum`采用 LINQ `Sum`法，所以为`System.Linq`增加一个`using`条款。为`System.Reflection`添加一个`using`声明来支持反思。

通过反射，一个`Type`实例可以让你访问关于一个类型的所有信息。`Main`方法调用`GetMethod`来获取对`Add`方法的`MethodInfo`引用，但是还有很多方法可以让您查看类型的各个部分。作为可用功能子集的一个示例，您可以调用`GetMethods`、`GetProperties`或`GetFields`分别获得一组`MethodInfo`、`PropertyInfo`或`FieldInfo`。`Type`类中还有很多方法可以使用，用这个写代码练习是一个很有趣的练习。

`Activator.CreateInstance`创建它所传递的`Type`的新实例。调用`Invoke`可以让你运行一个方法并得到结果。为了简单起见，前面的大部分反射代码都是硬编码的，但是当您需要编写代码来检查另一段代码的功能并可选地使用一个类型的成员时，这非常有用。

## 动态处理代码

C# 还有一种叫做 dynamic 的类型。它的目的是允许您与动态语言(如 IronPython 和 IronRuby)进行互操作，并使反射更加容易。微软也有一项叫做 Silverlight 的技术。动态可以使使用 HTML DOM 变得更容易，但是作为一种动态网络应用技术，Silverlight 在很大程度上已经被 HTML 5 所取代。

`dynamic`类型允许您为`dynamic`变量赋值，并在该变量上使用任何类型化成员。任何错误都由运行时的 CLR 处理，而不是由 C# 编译器发出错误。您可能会发现，通过编码的灵活性，这有很大的优势，但是动态类型的一个缺点是，它直到运行时才提供类型相关错误的指示。使用前面例子中的`FinancialCalculator`类，下面是一些动态代码的例子。

```cs
    using System;
    using System.Reflection;

    public class Program
    {
        public static void Main()
        {
            decimal[] prices = { 1m, 2m, 3m };

            Type calcType = typeof(FinancialCalculator);
            MethodInfo sumMethod = calcType.GetMethod("Sum");
            dynamic calc = Activator.CreateInstance(calcType);
            dynamic sum = calc.Sum(prices);

            Console.WriteLine($"Sum: {sum}\nPress any key to continue.");
            Console.ReadKey();
        }
    }

```

代码清单 120

您可能会注意到，这段代码比完全反射实现更简单。您没有接口，也不能保证`calc`实例有一个名为`Sum`的成员，但是由于替代方法是使用反射，所以您仍然处于相同的运行时评估情况。因此，对于这个特定的场景，这可能是一个合理的方法。

将您所了解的泛型集合在一起，进一步改进算法可能会很有用，如下面的清单所示。

```cs
    using System;

    public class Program
    {
        public static void Main()
        {
            decimal[] prices = { 1m, 2m, 3m };

            decimal sum = GetSum<FinancialCalculator, decimal>(prices);

            Console.WriteLine("Sum: {0}\nPress any key to continue.", sum);
            Console.ReadKey();
        }

        public static TValue GetSum<TCalc, TValue>(TValue[] prices)
            where TCalc : new()
        {
            dynamic calc = new TCalc();
            TValue sum = calc.Sum(prices);
            return sum;
        }
    }

```

代码清单 121

前面的例子完全消除了反射的需要，减少了代码，使算法在需要的地方强类型化，并在有帮助的地方动态化。本来可以使用接口约束来使`GetSum`更强类型化，但是我用这个作为练习来帮助你思考动态可能在哪里有用。

## 总结

属性是 C# 的特性，它告诉工具关于你的代码的一些事情。反射帮助您编写可以评估和执行其他代码的元代码。有一种动态类型允许您对正在编写的代码进行假设，与动态语言进行交互，并使其易于执行反射。

这就简洁地完成了 *C# 的*。我希望它对你有用。祝你在今后的学习中一切顺利。