C#中的特性和反射:使代码灵活而强大的利器

本文详细介绍了C#中的特性(Attribute)和反射(Reflection),包括它们的定义、使用场景、特性和注释的区别,以及常见的预定义特性如AttributeUsage、Conditional和Obsolete。同时涵盖了如何创建和使用特性,以及反射的实例应用和优缺点分析。
摘要由CSDN通过智能技术生成

目录

引言

特性(Attribute)

特性和注释的区别       

创建和使用特性

常用的预定义的特性

AttributeUsage

Conditional

Obsolete

反射(Reflection)

使用反射读取特性

更深入地使用反射

反射的常见用途

反射的优缺点

如何优雅的使用反射

总结


引言

        在C#编程的世界里,特性(Attribute)和反射(Reflection)是两个强大的概念,它们为开发者提供了极大的灵活性和编程的便捷性。特性允许我们在不修改程序逻辑的前提下,为代码添加额外的信息,而反射则提供了在运行时查看和操作程序元素的能力。本文将结合实际经验和提供的文档内容,深入浅出地探讨这两个概念,并展示如何在实际开发中应用它们。

特性(Attribute)

        特性是一种特殊的修饰符,以方括号[]包裹,附着在C#程序的各种元素(如类、方法、属性、字段等)之上,为它们添加额外的元数据。这些元数据不仅可以在编译时影响编译器的行为,更重要的是,它们能在运行时通过反射被查询和利用,为程序提供丰富的自描述信息和动态行为指导。例如,我们可能会在代码中标注“这个类是我负责编写的”或者“这个函数在过去的开发周期中曾经出现过性能问题”。这样的信息对于团队协作和代码维护都是非常有价值的。

特性和注释的区别       

特性与注释虽然都用于提供额外信息,但存在本质区别:

  • 注释:仅面向人类阅读,编译器编译时忽略,不影响程序行为。
  • 特性:既是源代码一部分,也被编译进程序集元数据,可在运行时通过反射访问,直接影响程序行为。

创建和使用特性

        特性的基本语法包括将特性片段放置在目标声明之前,用方括号[]包裹特性名及其参数(如果有的话)。

// 无参数特性 [Serializable]

public class MyClass { // 类的实现 }



// 带参数特性 [MyAttribute("first", "second", "finally")]

public class MyClass { // 类的实现 }

一个声明可以同时应用多个特性,特性之间可以用逗号分隔,或者分别声明。这为代码提供了灵活的修饰方式。

//多个独立特性片段
[Serializable]

[MyAttribute("first", "second", "finally")]

public class MyClass { // 类的实现 }

// 特性通过逗号分隔

[MyAttribute("first", "second", "finally"), Serializable]

public class MyClass { // 类的实现 }

此外,.NET框架允许某些特性被多次应用到同一个程序元素上。例如,Conditional特性就可以根据预定义的条件来控制方法的执行。


[Conditional("DEBUG"), Conditional("TEST1")]
 public void TraceMethod() { // 方法实现 }

在这个例子中,TraceMethod只有在预处理器指令DEBUGTEST1被定义时才会被执行,这为条件性地执行代码提供了一种灵活的机制。

常用的预定义的特性

.NET框架为我们提供了一些预定义的特性,如AttributeUsageConditionalObsolete等。这些特性有着特定的用途和行为。

  • AttributeUsage:定义了自定义特性的使用规则,比如它可以应用到哪些类型的声明上,是否可以多次应用等。
  • Conditional:用于条件编译,只有在定义了特定的预处理指令时,标记为Conditional的方法才会被执行。
  • Obsolete:标记不再推荐使用的程序元素,可以提供替代方案,并指定是生成警告还是错误。

AttributeUsage

        预定义特性 AttributeUsage 描述了如何使用一个自定义特性类。它规定了特性可应用到的项目的类型。规定该特性的语法如下:


[AttributeUsage(
   validon,
   AllowMultiple=allowmultiple,
   Inherited=inherited
)]

其中:

        参数 validon 规定特性可被放置的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All。
        参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。
         参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不被继承)。

例如:

[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property, 
AllowMultiple = true)]

Conditional

        这个预定义特性标记了一个条件方法,其执行依赖于指定的预处理标识符。

        它会引起方法调用的条件编译,取决于指定的值,比如 Debug 或 Trace。例如,当调试代码时显示变量的值。

//#define CONDITINA
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

///Conditional
namespace _01_特性
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Test1();
            Test2();
        }
        [Conditional("CONDITINA")]
        public static void Test1()
        {
            Console.WriteLine("Test1");
        }
        public static void Test2()
        {
            Console.WriteLine("Test2");
        }
    }
}

Obsolete

        这个预定义特性标记了不应被使用的程序实体。它可以让您通知编译器丢弃某个特定的目标元素。例如,当一个新方法被用在一个类中,但是您仍然想要保持类中的旧方法,您可以通过显示一个应该使用新方法,而不是旧方法的消息,来把它标记为 obsolete(过时的)。

 规定该特性的语法如下:

[Obsolete(
   message
)]
[Obsolete(
   message,
   iserror
)]

其中:

        参数 message,是一个字符串,描述项目为什么过时以及该替代使用什么。
        参数 iserror,是一个布尔值。如果该值为 true,编译器应把该项目的使用当作一个错误。默认值是 false(编译器生成一个警告)。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _02_特性
{
    internal class Program
    {
        static void Main(string[] args)
        {
            OldMethod();
            NewMethod();
        }
        [Obsolete("这个方法过时了,使用NewMethod来代替",true)]
        public static void OldMethod()
        {
            Console.WriteLine("旧方法");
        }
        public static void NewMethod()
        {
            Console.WriteLine("新方法");
        }
    }
}

反射(Reflection)

反射是.NET提供的一个强大的库,它允许我们在运行时取得任何类型的信息,并能创建类型实例、调用方法、获取或设置属性等。

使用反射读取特性

下面的代码演示了如何使用反射来检索我们前面定义的DeveloperAttribute

public static void DisplayDeveloperInfo(Type type)
{
    // 获取应用在类型上的DeveloperAttribute
    DeveloperAttribute devAttr = (DeveloperAttribute)Attribute.GetCustomAttribute(type, typeof(DeveloperAttribute));
    
    if (devAttr != null)
    {
        Console.WriteLine($"{type.Name} was developed by {devAttr.Name}, level: {devAttr.Level}");
    }
}

public static void Main()
{
    DisplayDeveloperInfo(typeof(MyAmazingClass));
}

当运行这段代码时,你将看到类MyAmazingClass的元数据被打印出来,这是因为我们使用了反射来检索类装饰的特性信息。下面是另一个例子,更加详细的写法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _03_自定义特性
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //4.通过反射访问特性
            //Type获取有关加载的程序集中其中定义的类型信息
            //typeof用于获取类型的System.Type对象
            Type type = typeof(Test);
            //获取自定义属性
            var someAttributes = type.GetCustomAttributes(typeof(Some), true);
            var somethingAttributes = type.GetCustomAttributes(typeof(Something), true);

            foreach (var a in someAttributes.Concat(somethingAttributes))
            {
                Console.WriteLine(((dynamic)a).Name + ((dynamic)a).Date);
            }
        }
        
        //1.声明自定义特性
        [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
        //2.构建自定义特性
        public class Something : Attribute
        {
            public string Name { get; set; }
            public string Date { get; set; }

            public Something(string Name, string Date)
            {
                this.Name = Name;
                this.Date = Date;
            }
        }

        [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
        public class Some : Attribute
        {
            public string Name { get; set; }
            public string Date { get; set; }

            public Some(string Name, string Date)
            {
                this.Name = Name;
                this.Date = Date;
            }
        }
		//3.在目标程序元素上应用自定义特性
        [Something("刘某", "2020-11-12")]
        [Something("万某", "2018-01-12")]
        [Some("周某", "2018-05-12")]
        [Some("末某", "2018-03-12")]

        class Test
        {

        }
    }
}

更深入地使用反射

反射不仅可以读取元数据,它还能让我们实例化对象、调用方法或访问字段和属性。这使得C#成为一种非常动态的语言,可以在运行时做出许多编译时无法确定的决定。

让我们看看如何使用反射来调用一个方法。

public class ReflectionDemoClass
{
    private int _x;

    public ReflectionDemoClass(int x)
    {
        _x = x;
    }

    public int AddToX(int y)
    {
        return _x + y;
    }
}

public static void Main()
{
    // 创建ReflectionDemoClass的实例
    Type demoType = typeof(ReflectionDemoClass);
    object demoInstance = Activator.CreateInstance(demoType, new object[] { 10 });

    // 调用AddToX方法
    MethodInfo addMethod = demoType.GetMethod("AddToX");
    int result = (int)addMethod.Invoke(demoInstance, new object[] { 5 });
    
    Console.WriteLine(result); // 输出15
}

在这个例子中,我们��建了ReflectionDemoClass的实例并调用了它的AddToX方法,尽管我们在编译时并不知道这个类或它的方法。这给我们编写通用代码或框架提供了强大的灵活性。

反射的常见用途

  1. 单元测试: 反射通常用于单元测试框架中,来自动发现并运行测试方法。
  2. 动态加载: 通过反射,程序可以在运行时加载和使用外部库,即便这些库在编译时不可用。
  3. 依赖注入: 反射是实现依赖注入框架的关键,例如.NET Core中的依赖注入容器。

反射的优缺点

优点:

  1.  反射提高了程序的灵活性和扩展性。
  2.  降低耦合性,提高自适应能力。
  3.  它允许程序创建和控制任何类的对象,无需提前硬编码目标类。

缺点

  1. 性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
  2. 使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。

如何优雅的使用反射

反射非常强大但同时也可能增加出错的风险,因为它依赖于字符串(例如方法名),这可能会在运行时出现问题。我们可以通过多种方式来减少风险:

  • 使用nameof运算符来获取变量、类型或方法的名称
  • 捕获和处理Reflection可能抛出的异常。
  • 使用接口或委托定义明确的契约,而不是直接使用反射调用方法。

总结

特性和反射,是C#语言中两个极其有用的功能。特性提供了一种注解代码的方法,允许我们在编译时和运行时访问这些注解。而反射,则提供了一种强大的工具,它让我们可以查询和调用代码的结构和行为,即使在编写代码的时候我们不知道那些结构的细节。

通过以上内容的学习,希望你已经对C#中的特性和反射有了基本的认识,欢迎你在实际项目中尝试使用这些强大的工具,来提升你的代码的灵活性和强大功能。

觉得本篇文章写的还不错可以点赞,收藏,关注。主页有21天速通C#教程欢迎订阅!!!

  

  • 24
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值