演练C#特性

目录

介绍

背景

什么是特性?

关于特性要记住的事情

特性类型

内在特性

常用的内置特性

使用C#通过反射提取内置类型

在运行时读取特性

自定义特性

创建自定义特性

在类中应用自定义特性

限制特性的使用


介绍

如果您已经使用C#语言已有一段时间了,那么您正在使用内置特性(例如[Serializable], [Obsolete]),但是您还没有对此进行深入的考虑?在本文中,我们将探讨特性的基础知识,常见的特性,如何创建和读取特性。令人兴奋的是,您将看到如何使用System.Reflection来获取内置特性。好,那就开始吧。

背景

这有点题外话,但可以分享。你知道吗?当我们放松和阅读书籍时,我们的头脑往往会开始怀疑。而且,当我读一本C#书籍时,这件事发生在我身上,我开始想知道如何通过System.Reflection来获取那些内置特性。因此,这篇文章变得生动起来。

什么是特性?

特性很重要,它提供了其他信息,为开发人员提供了线索。尤其是在期望的情况下,具有类的行为以及应用程序中类的特性和/或方法。

简而言之,特性就像形容词,描述类型、程序集、模块、方法等。

关于特性要记住的事情

  • 特性是派生自System.Attribute的类 
  • 特性可以具有参数
  • 在代码中使用特性时,特性可以省略特性名称的Attribute部分。框架将以任何一种方式正确处理特性。

特性类型

内在特性

这些特性也称为预定义或内置特性。.NET Framework / .NET Core提供了数百甚至数千个内置特性。它们中的大多数是专门的,但是我们将尝试以编程方式提取它们,并讨论一些最常见的方法。

常用的内置特性

特性

描述

[Obsolete]

System.ObsoleteAttribute
帮助您识别代码应用程序中的过时位。

[Conditional]

System.Diagnostics.ConditionalAttribute
使您能够执行条件编译。

[Serializable]

System.SerializableAttribute
表明一个类可以序列化。

[NonSerialized]

System.NonSerializedAttribute
表明不应将可序列化类的字段序列化。

[DLLImport]

System.DllImportAttribute
显示方法由非托管动态链接库(DLL)公开为静态入口点。

使用C#通过反射提取内置类型

如所承诺的,我们将看到如何使用C#提取内置特性。请参见下面的示例代码:

using System;
using System.Linq;
using System.Reflection;
using Xunit;
using Xunit.Abstractions;

namespace CSharp_Attributes_Walkthrough {
    public class UnitTest_Csharp_Attributes {
        private readonly ITestOutputHelper _output;

        private readonly string assemblyFullName = "System.Private.CoreLib, 
                Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e";

        public UnitTest_Csharp_Attributes (ITestOutputHelper output) {
            this._output = output;
        }

        [Fact]
        public void Test_GetAll_BuiltIn_Attributes () {
            var assembly = Assembly.Load (assemblyFullName);

            var attributes = assembly
                .DefinedTypes
                .Where (type =>
                    type
                    .IsSubclassOf (typeof (Attribute)));

            foreach (var attribute in attributes) {
                
                string attr = attribute
                    .Name
                    .Replace ("Attribute", "");

                this._output
                    .WriteLine ("Attribute: {0} and Usage: [{1}]", attribute.Name, attr);
            }
        }
    }
}

请参见下面的输出:

在运行时读取特性

现在,我们已经回答了什么是特性,什么是常用特性,以及如何通过System.Reflection提取内置特性的方法,让我们看看如何在运行时读取这些特性,当然使用System.Reflection

在运行时检索特性值时,有两种方法可以检索值。

  • 使用GetCustomAttributes()方法,这将返回一个包含指定类型的所有特性的数组。当您不确定哪些特性适用于特定类型时,可以使用此方法,可以遍历此数组。
  • 使用GetCustomAttribute()方法,这将返回所需的特定特性的详细信息。

好的,接下来让我们来看一个例子。

让我们首先尝试创建一个类,并使用一些随机特性对其进行标记。

using System;
using System.Diagnostics;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    [Serializable]
    public class Product
    {
        public string Name { get; set; }
        public string Code { get; set; }

        [Obsolete("This method is already obselete. Use the ProductFullName instead.")]
        public string GetProductFullName()
        {
            return $"{this.Name} {this.Code}";
        }

        [Conditional("DEBUG")]
        public void RunOnlyOnDebugMode()
        {

        }
    }
}

Product-class是很容易理解。在下面的示例中,我们要检查以下内容:

  • 检查Product-class是否确实具有[Serializable]特性。
  • 检查Product-class是否具有两个方法。
  • 检查每个方法是否确实具有特性。
  • 检查方法GetProductFullName是否正在使用[Obsolete]特性。
  • 检查方法RunOnlyDebugMode是否正在使用[Conditional]特性。
/*
*This test will read the Product-class at runtime to check for attributes. 
*1. Check if [Serializable] has been read. 
*2. Check if the product-class has two methods 
*3. Check if each methods does have attributes. 
*4. Check if the method GetProudctFullName is using the Obsolete attribute. 
*5. Check if the method RunOnlyOnDebugMode is using the Conditional attribute.
*/
[Fact]
public void Test_Read_Attributes()
{
    //get the Product-class
    var type = typeof(Product);

    //Get the attributes of the Product-class and we are expecting the [Serializable]
    var attribute = (SerializableAttribute)type.
                    GetCustomAttributes(typeof(SerializableAttribute), false).FirstOrDefault();

    Assert.NotNull(attribute);

    //Check if [Serializable] has been read.
    //Let's check if the type of the attribute is as expected
    Assert.IsType<SerializableAttribute>(attribute);

    //Let's get only those 2 methods that we have declared 
    //and ignore the special names (these are the auto-generated setter/getter)
    var methods = type.GetMethods(BindingFlags.Instance | 
                                    BindingFlags.Public | 
                                    BindingFlags.DeclaredOnly)
                        .Where(method => !method.IsSpecialName).ToArray();

    //Check if the product-class has two methods 
    //Let's check if the Product-class has two methods.
    Assert.True(methods.Length == 2);

    Assert.True(methods[0].Name == "GetProductFullName");
    Assert.True(methods[1].Name == "RunOnlyOnDebugMode");

    //Check if each methods does have attributes. 
    Assert.True(methods.All( method =>method.GetCustomAttributes(false).Length ==1));

    //Let's get the first method and its attribute. 
    var obsoleteAttribute = methods[0].GetCustomAttribute<ObsoleteAttribute>();

    // Check if the method GetProudctFullName is using the Obsolete attributes. 
    Assert.IsType<ObsoleteAttribute>(obsoleteAttribute);

    //Let's get the second method and its attribute. 
    var conditionalAttribute = methods[1].GetCustomAttribute<ConditionalAttribute>();

    //Check if the method RunOnlyOnDebugMode is using the Conditional attributes.
    Assert.IsType<ConditionalAttribute>(conditionalAttribute);
}

希望您喜欢上面的示例。然后让我们进入自定义特性。

自定义特性

内置特性是有用且重要的,但是在大多数情况下,它们具有特定用途。此外,如果您认为出于某种原因需要特性而不考虑内置特性,则可以创建自己的特性。

创建自定义特性

在本部分中,您将看到我们如何创建自定义特性,以及在创建自定义特性时需要记住的内容。

  • 要创建自定义特性,请定义一个派生自System.Attribute的类。
using System;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    public class AliasAttribute : Attribute
    {
        //This is how to define a custom attributes.
    }
}
  • 位置参数——如果您的自定义特性的构造函数中有任何参数,它将成为必需的位置参数。
using System;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    public class AliasAttribute : Attribute
    {
        /// <summary>
        /// These parameters will become mandatory once have you decided to use this attribute.
        /// </summary>
        /// <param name="alias"></param>
        /// <param name="color"></param>
        public AliasAttribute(string alias, ConsoleColor color)
        {
            this.Alias = alias;
            this.Color = color;
        }

        public string  Alias { get; private set; }
        public ConsoleColor Color { get; private set; }
    }
}
  • 可选参数 ——这些是从System.Attribute派生的类的public字段和public可写特性。
using CSharp_Attributes_Walkthrough.My_Custom_Attributes;
using System;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    public class AliasAttribute : Attribute
    {
        //....

        //Added an optional-parameter
        public string AlternativeName { get; set; }
    }
}

请参见下面的完整示例代码:

using System;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    public class AliasAttribute : Attribute
    {
        /// <summary>
        /// These parameters will become mandatory once have you decided to use this attribute.
        /// </summary>
        /// <param name="alias"></param>
        /// <param name="color"></param>
        public AliasAttribute(string alias, ConsoleColor color)
        {
            this.Alias = alias;
            this.Color = color;
        }

        #region Positional-Parameters
        public string Alias { get; private set; }
        public ConsoleColor Color { get; private set; }
        #endregion 

        //Added an optional-parameter
        public string AlternativeName { get; set; }
    }
}

请参见下图,以可视化位置参数和可选参数之间的差异。

现在我们已经创建了一个自定义特性,让我们尝试在一个类中使用它。

在类中应用自定义特性

using System;
using System.Linq;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    [Alias("Filipino_Customers", ConsoleColor.Yellow)]
    public class Customer
    {
        [Alias("Fname", ConsoleColor.White, AlternativeName = "Customer_FirstName")]
        public string Firstname { get; set; }

        [Alias("Lname", ConsoleColor.White, AlternativeName = "Customer_LastName")]
        public string LastName { get; set; }

        public override string ToString()
        {
            //get the current running instance.
            Type instanceType = this.GetType(); 

            //get the namespace of the running instance.
            string current_namespace = (instanceType.Namespace) ?? "";

            //get the alias.
            string alias = (this.GetType().GetCustomAttributes(false).FirstOrDefault() 
                            as AliasAttribute)?.Alias;

            return $"{current_namespace}.{alias}";
        }
    }
}

该示例的主要内容是ToString()方法,默​​认情况下将返回该类型的全限定名称。然而我们在这里所做的是,我们已经覆盖了ToString()返回特性的全限定名称和别名的方法。

现在让我们尝试调用该ToString()方法,看看它返回什么。请参见下面的示例,输出:

using CSharp_Attributes_Walkthrough.My_Custom_Attributes;
using System;

namespace Implementing_Csharp_Attributes_101
{
    class Program
    {
        static void Main(string[] args)
        {
            var customer = new Customer { Firstname = "Jin Vincent" , LastName = "Necesario" };
          
            var aliasAttributeType = customer.GetType();

            var attribute = 
                aliasAttributeType.GetCustomAttributes(typeof(AliasAttribute), false);

            Console.ForegroundColor = ((AliasAttribute)attribute[0]).Color;

            Console.WriteLine(customer.ToString());

            Console.ReadLine();
        }
    }
}

限制特性的使用

默认情况下,您可以将自定义特性应用于应用程序代码中的任何实体。因此,当您创建自定义特性时,它可以应用于类、方法、私有字段、特性、结构等。但是,如果您希望将自定义特性限制为仅出现在某些类型的实体上。您可以使用AttributeUsage特性来控制可以将其应用于哪些实体。

目标

AttributeTargets.All

可以应用于应用程序中的任何实体

AttributeTargets.Assembly

可以应用于程序集

AttributeTargets.Class

可以应用于类

AttributeTargets.Construtor

可以应用于构造函数

AttributeTargets.Delegate

可以应用于代理

AttributeTargets.Enum

可以应用于枚举

AttributeTargets.Event

可以应用于事件

AttributeTargets.Field

可以应用于字段

AttributeTargets.Interface

可以应用于接口

AttributeTargets.Method

可以应用于方法

AttributeTargets.Module

可以应用于模块

AttributeTargets.Parameter

可以应用于参数

AttributeTargets.Property

可以应用于属性

AttributeTargets.ReturnValue

可以应用于返回值

AttributeTargets.Struct

可以应用于结构

如果您想知道,我们如何在运行时获得这些AttributeTargets?请参阅以下示例:

[Fact]
public void Test_GetAll_AttributeTargets()
{
    var targets = Enum.GetNames(typeof(AttributeTargets));

    foreach (var target in targets)
    {
        this._output.WriteLine($"AttributeTargets.{target}");
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
未来的视窗、组件程序的设计潮流,将是采用.NET平台为底层服务,并使用C#语言开发。C#是微软为.NET技术量身订制的语言,它拥有简洁的语法和便于开发的特性。本书将C#的特色与应用,借助实战演练的方式,阶段性地引导读者建置完整的应用程序,是您掌握新技术的最佳捷径。【图书目录】-C#与.NET技术平台实战演练PARTI基础语法篇第1章.NET概述1-1 软件开发结构的演进1-2 桌上型单机应用程序1-3 主从结构应用程序1-4 分布式应用程序结构1-4-1 WindowsDNA(WindowsDistributedInternetArchitecture)1-4-2 WindowsDNA20001-5 下一代的互联网平台:Microsoft.NET1-5-1.NETFramework1-5-2 公共语言执行环境1-5-3 类函数库1-5-4 .NET程序语言第2章C#概论2-1C#应用程序的结构2-1-1 分析C#应用程序结构2-1-2 Using前置命令与System名称空间2-2 标准的输入/输出2-2-1 输出字符串到Console2-2-2格式化输出字符申2-2-3 读取数据2-3 程序代码注释生成XML文件2-4 编译C#应用程序编译器选项第3章实值类型变量3-1 公共类型系统3-2 实值类型与参考类型实值类型与参考类型的差异3-3 找寻基础类3-4 实值类型3-4-1 简单类型3-4-2 命名原则3-4-3 定义变量3-4-4 指定变量的值3-5常用的操作数使用递增与递减操作数3-6 操作数优先顺序3-7 使用列举类型3-7-1enum类型常用的方法3-7-2使用enum类型常犯的错误3-8使用struct类型3-9 数据类型转换3-9-1 隐含式转换3-9-2明确式转换第4章语句与例外处理4-1 程序区块4-2 语句类型4-2-1 if语句4-2-2 串联if(cascadingif)4-2-3switch语句4-2-4在switch中使用goto4-3重复执行的语句4-3-1while语句4-3-2do语句4-3-3for语句4-3-4foreach语句4-4跳转语句4-4-lgoto语句4-4-2break与continue语旬4-5例外错误4-5-1生成例外错误4-5-2检查数值溢出第5章方法与参数5-l定义方法定义方法的语法5-2调用其他类的方法使用return跳出程序区段5-3声明局域变量5-4共用变量变量范围冲突5-5使用返回值非void的方法必须有返回值5-6声明与使用参数声明参数的语法5-7参数传递机制5-7-1使用传值参数5-7-2使用传出参数5-7-3使用传参考参数5-8使用传参考参数常见的错误5-9声明不定长度的参数使用foreach语句5-10使用递回方法5-11覆写方法第6章数组6-1什么是数组?6-2声明数组6-3数组维数6-4存取数组元素6-5使用Length属性检查数组上下限6-6 数组与集合的比较6-7 声明时初始化数组6-7-1 使用简短的表示法6-7-2 初始化多维的数组6-8 执行时期指定数组的大小6-9 JaggedArray6-10 复制数组变量6-11 数组的应用6-11-l 数组常用的属性6-11-2 数组常用的方法6-11-3 由方法返回一个数组6-11-4 把数组当参数传递6-12 命令提示符参数第7章面向对象程序设计7-1 面向对象的缘由7-1-1 增加程序代码重复使用7-1-2 原始程序代码共用阶段7-2 类7-3 名称空间7-4 降低维护的负担7-5 数据封装7-6 继承7-7 多态7-8 抽象化使用接口第8章类.名称空间8-1 类与对象8-l-1 类的成员8-l-2 对象8-1-3 定义类的语法8-2 类与结构的比较结构适用于"轻量型"的对象8-3 数据封装8-4 数据与方法的存取控制8-4-1 使用成员访问修饰符8-4-2 良好的数据隐藏方式设计8-4-3 数据封装的目的8-5 使用静态成员8-5-1 声明及初始化静态数据8-5-2 使用静态方法8-5-3 静态成员使用准则8-6 C#与面向对象8-6-1 再探HelloWorld8-6-2以static定义Main8-7定义类与建立实体this操作数8-8使用访问修饰符8-9建立嵌套类8-10名称空间8-10-1声明名称空间8-10-2名称空间的领域8-10-3使用名称空间的好处8-10-4名称空间存取控制8-11完全区别名称8-12使用using前置命令建立阶层式类结构8-13使用别名8-14使用命名空间的准则第9章参考类型变量9-1使用参考类型变量9-2参考类型与实值型9-2-1实值类型9-2-2参考类型9-2-3实值类型与参考类型差异9-3声明与释放参考类型变量9-3-1声明参考类型变量9-3-2释放参考类型变量9-4比较参考类型变量与实值类型变量9-5多个参考类型变量参考到同一个对象9-6以参考变量当做方法的参数9-7常用的参考类型9-7-1Exception类9-7-2String类9-7-3比较字符串的方法与操作数9-7-4C#对象阶层结构9-8.NETFramework常用的类9-8-1System.IO9-8-2System.XML9-9数据类型转换9-9-1隐含式转换9-9-2明确式转换9-10Parent与Child之间的转换9-10-1转换成ParentClass的参考9-10-2转换成ChildClass的参考9-11使用is操作数9-12使用as操作数9-13Object类型转换守则9-14Boxing与Unboxing9-14-1Boxing9-14-2UnBoxing第10章对象的生与死10-1了解构造器10-1-1使用new取得内存10-1-2ManagedHeap内存配置10-1-3初始化对象10-2默认构造器10-2-l默认构造器的特性10-2-2编译器自动生成的构造器10-2-3定义默认构造器10-2-4定义构造器原则10-3覆写构造器10-4覆写构造器潜在的问题10-4-1解决重复初始化程序代码的问题10-4-2使用consbuctor-initializer10-5初始化只读数据10-6在构造器中使用out与ref10-7struct构造器10-7-1struct构造器的限制10-8static构造器10-8-1使用static构造器初始化静态成员10-8-2static构造器的限制10-9对象与内存10-9-l对象的生命周期10-9-2局域变量的生命周期10-9-3对象的生命周期10-10谁来"摧毁"对象?C#借由回收站回收资源10-11使用Finalize方法Finalize对效率的影响10-12编写析构器使用析构器的考虑10-13实现IDisposable接口第11章继承.多态与接口11-l扩充基础类的功能11-1-l继承的语法11-l-2简单的UML描述11-2扩充类的继承11-2-1使用访问修饰符控制存取权限11-2-2protected成员的继承11-3调用基础类的构造器声明构造器ll-4改写基础类的方法11-4-1定义虚拟方法ll-4-2虚拟方法与改写方法的守则11-5隐藏基础类方法11-6使用sealed类与sealed方法11-7版本控制11-8使用接口接口的特点11-9 以明确的方式实现接口11-10 抽象类11-10-1 抽象方法11-10-2 不实现接口的抽象类11-11 抽象类与接口11-12 抽象方法与版本控制第12章属性与索引12-1 组件定义12-2 属性12-2-1 为什么要用属性?12-2-2 使用属性的好处12-3 属性的种类12-4 使用存取元12-4-1 使用get存取元12-4-2 使用set存取元12-4-3 使用get.set存取元12-5 编译器运作的情形12-6 属性与类数据成员的比较12-6-1 属性是逻辑上的类数据成员12-6-2 属性和数据成员的相似性12-6-3 属性和数据成员的不同点12-7 存取属性与类数据成员的比较12-8 属性与方法的比较12-8-l 属性和方法之间的相似性12-8-2 属性和方法之间的不同点12-9 索引的使用12-9-1 为什么要使用索引?12-9-2 定义索引12-10 索引和数组的比较12-11 覆写Indexer12-12 Indexer与属性的比较12-12-1 Indexer与属性的相似性12-12-2Indexer与属性的不同点12-l3Indexer设计准则第13章操作数.Delegate事件13-1 覆写操作数13-1-1 覆写操作数的语法13-1-2 覆写相同的操作数多次13-1-3 Conversion操作数l3-2 建立并使用delegate类型建立delegate类型l3-3 事件13-3-1 在事件发行者中定义一个事件13-3-2 在事件发行者中触发事件13-3-3 在事件订阅者中定义事件处理常式13-3-4 向事件发行者订阅一个事件l3-4 链接.删除事件设计准则l3-5 .NETFramewoek事件设计准则13-6传递事件的参数13-6-1Sender对象与事件参数13-6-2定义delegate函数样板13-6-3EventArgs类的设计l3-6-4事件处理常式的编写13-6-5触发事件第14章Attribute与Iteflection14-l何谓Attribute?14-2使用AttributeGlobalAttribute14-3自定义Attribute类14-3-1Attribute适用的元素14-3-2设计Attribute类14-3-3使用Attribute14-3-4Attribute编译的过程14-3-5使用positional参数与named参数14-3-6取得Attribute的值14-4Renection14-4-1Renection设计理念14-4-2MetadataPARTII实战演练篇第15章编写数据库应用程序15-1建立用户界面15-2编写公用函数15-3读取数据库数据,以及建立DataSet15-4自定义dataGrid展示样式15-5添加.删除.修改数据15-6增加核对程序运作的程序代码第16章编写Master/Detail数据库应用程序16-1建立Master/Detail关系的DataSet16-2自定义dataGrid展示样式16-3设计显示明细数据的表单16-4 使用BindingContext浏览数据第17章设计WindowsFrom应用程序17-1 设计MDI应用程序17-1-1 使用StatusBar与Timer控件17-1-2 设计选单17-1-3 编写选单程序代码17-2 设计GDI十应用程序17-2-1 绘制统计图表17-2-2 使用FontDialog设置字体17-2-3 设置打印格式17-2-4 打印预览与打印报表17-2-5 使用PrintDialog选择打印机17-2-6 保存图形文件第18章使用COM+服务18-1 编写.NET组件18-2 编写转帐类18-3 建立KeyFile与StrongName18-4 建立客户端的接口18-5 测试COM+应用程序第19章编写一个监控文件事务的WindowsService19-1使用WindowsService模板19-2使用EventLog与FileSystemWatcher控件19-3使用Installer类19-4安装WindowsServics19-5启动服务与暂停服务第20章编写文件管理器20-1 建立UCOMFileManger用户界面20-2 编写初始化应用程序的程序代码20-3 编写事件程序20-4 打开文本文件第21章编写提供数据的WebService21-1 建立WebService21-2 编写使用WebService的WindowsForms客户端第22章编写ASP.NET网页22-1建立ASP.NET项目22-2设计分页22-3增加编辑数据的按钮22-4编写修改.取消.保存按钮事件处理常式22-5编写添加功能22-6编写删除功能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值