编写高质量代码改善C#程序的157个建议[优先考虑泛型、避免在泛型中声明静态成员、为泛型参数设定约束]

简介: 原文:编写高质量代码改善C#程序的157个建议[优先考虑泛型、避免在泛型中声明静态成员、为泛型参数设定约束]前言   泛型并不是C#语言一开始就带有的特性,而是在FCL2.0之后实现的新功能。基于泛型,我们得以将类型参数化,以便更大范围地进行代码复用。
原文: 编写高质量代码改善C#程序的157个建议[优先考虑泛型、避免在泛型中声明静态成员、为泛型参数设定约束]

前言

  泛型并不是C#语言一开始就带有的特性,而是在FCL2.0之后实现的新功能。基于泛型,我们得以将类型参数化,以便更大范围地进行代码复用。同时,它减少了泛型类及泛型方法中的转型,确保了类型安全。委托本身是一种引用类型,它保存的也是托管堆中对象的引用,只不过这个引用比较特殊,它是对方法的引用。事件本身也是委托,它是委托组,C#中提供了关键字event来对事件进行特别区分。一旦我们开始编写稍微复杂的C#代码,就肯定离不开泛型、委托和事件。本章将针对这三个方面进行说明。

  这里也有一篇之前我对泛型的简单理解篇 http://www.cnblogs.com/aehyok/p/3384637.html C# 泛型的简单理解(安全、集合、方法、约束、继承)

本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要学习记录以下内容:

  建议32、总是优先考虑泛型

  建议33、避免在泛型类型中声明静态成员

  建议34、为泛型参数设定约束

建议32、总是优先考虑泛型

  泛型的优点是多方面的,无论是泛型类还是泛型方法都同时具备可重用性、类型安全和高效率等特性,这都是非泛型类和非泛型方法无法具备的。本建议将从可重用性、类型安全和高效率三个方面来进行剖析在实际的编码过程中为何总是应该优先考虑泛型。

一、可重用性,比如简单的设计一个集合类

    public class MyList
    {
        int[] items;
        public int this[int i]
        {
            get { return items[i]; }
            set { this.items[i] = value; }
        }

        public int Count
        {
            get { return items.Length; }
        }
        
        ////省略一些其他方法
    }

该类型只支持整型,如果要让类型支持字符串,有一种方法是重新设计一个类。但是这两个类型的属性和方法都是非常接近的,如果有一种方法可以让类型接收一个通用的数据类型,这样就可以进行代码复用了,同时类型也只要一个就够了。泛型完成的就是这样的功能。

    public class MyList<T>
    {
        T[] items;

        public T this[int i]
        {
            get { return items[i]; }
            set { this.items[i] = value; }
        }

        public int Count
        {
            get { return items.Length; }
        }

        ///省略其他方法
    }

  可以把T理解为一个占位符,在C#泛型编译生成的IL代码中,T就是一个占位符的角色。在运行时,即使编译器(JIT)会用实际代码中输入的T类型来代替T,也就是说,在由JIT生成的本地代码中,已经使用了实际的数据类型。我们可以把MyList<int>和MyList<string>视作两个完全不同的类型,但是,这仅是对本地代码而言的,对于实际的C#代码,它仅仅拥有一个类型,那就是泛型类型MyList<T>。

  以上从代码重用性的角度论证了泛型的优点。继续从类型MyList<T>的角度论述,如果不用泛型实现代码重用,另一种方法是让MyList的编码从object的角度去设计。在C#的世界中,所有类型(包括值类型和引用类型)都是继承自object,如果要让MyList足够通用,就需要让MyList针对object编码,代码如下:

    public class MyList
    {
        object[] items;
        public object this[int i]
        {
            get { return items[i]; }
            set { this.items[i] = value; }
        }

        public int Count
        {
            get { return items.Length; }
        }
        
        ////省略一些其他方法
    }

这会让以下代码编译通过

            MyList list = new MyList();
            list[0] = 123;
            list[1] = "123";

由上面两行代码带来的问题就是非”类型安全性“。该问题实际在建议20 http://www.cnblogs.com/aehyok/p/3641896.html 中已经详细论述过了。让类型支持类型安全,可以让程序在编译期间就过滤掉部分Bug,同时也能让代码规避掉”转型为object类型“或“从object转型为实际类型”所带来的效率损耗。尤其是涉及的操作类型是值类型时,还会带来装箱和拆箱的性能损耗。

例如,上文代码中的

list[1] = 123;

就会带来一次装箱操作,因为它首先倍转型为object,继而存储到items这个object数组中去了。

  泛型为C#带来的是革命性的变化,FCL之后的很多功能都是借助泛型才得到了很好的实现,如LINQ。LINQ借助于泛型和扩展方法,有效地丰富了集合的查询功能,同时避免了代码爆炸并提升了操作的性能。我们在设计自己的类型时,应充分考虑到泛型的优点,让自己的类型成为泛型类。

 

建议33、避免在泛型类型中声明静态成员

 在上一个建议中,已经解释了应该将MyList<int> 和MyList<string> 视作两个完全不同的类型,所以,不应将MyList<T>中的静态成员理解成为MyList<int>和MyList<string>共有的成员。

对于一个非泛型类型,以下的代码很好理解:

    public class MyList
    {
        public static int Count { get; set; }

        public MyList()
        {
            Count++;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            MyList myList1 = new MyList();
            MyList mylist2 = new MyList();
            Console.WriteLine(MyList.Count);
            Console.ReadLine();
        }
    }

结果返回为2.

如果将MyList换成泛型类型,看看下面的代码会输出什么呢?

    public class MyList<T>
    {
        public static int Count { get; set; }

        public MyList()
        {
            Count++;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            MyList<int> myList1 = new MyList<int>();
            MyList<int> mylist2 = new MyList<int>();
            MyList<string> mylist3 = new MyList<string>();
            Console.WriteLine(MyList<int>.Count);
            Console.WriteLine(MyList<string>.Count);
            Console.ReadLine();
        }
    }

代码输出为

实际上,随着你为T指定不同的数据类型,MyList<T>相应的也变成了不同的数据类型,在它们之间是不共享静态成员的。

不过,从上文我们也觉察到了,若T所指定的数据类型是一致的,那么两个泛型对象间还是可以共享静态成员的,如上文的myList1和myList2。但是,为了规避因此而引起的混淆,仍旧建议在实际的编码工作中,尽量避免声明泛型类型的静态成员。

上面举的例子是基于泛型类型的,非泛型类型中静态泛型方法看起来很接近该例子,但是应该始终这样来理解:

非泛型类型中的泛型方法并不会在运行时的本地代码中生成不同的类型。

    public class MyList
    {
        public static int Count { get; set; }
        public static int Func<T>()
        {
            return Count++;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(MyList.Func<int>());
            Console.WriteLine(MyList.Func<int>());
            Console.WriteLine(MyList.Func<string>());
            Console.ReadLine();
        }
    }

输出结果为

建议34、为泛型参数设定约束

 ”约束“这个词可能会引起歧义,有些人可能认为对泛型参数设定约束是限制参数的使用,实际情况正好相反。没有约束的泛型参数作用很有限,倒是”约束“让泛型参数具有了更多的行为和属性。

    public class Salary
    {
        /// <summary>
        /// 姓名
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 基本工资
        /// </summary>
        public int BaseSalary { get; set; }

        /// <summary>
        /// 奖金
        /// </summary>
        public int Bouns { get; set; }

    }

    public class SalaryComputer
    {
        public int Compare<T>(T t1, T t2)
        {
            return 0;
        }
    }

 

查看上面定义实体类可以发现,Compare<T>方法的参数t1或参数t2仅仅具有object的属性和行为,所以几乎不能在方法中对它们做任何的操作。但是,在加了约束之后,我们会发现参数t1或参数t2变成了一个有用的对象。由于为其指定了对应的类型,t1和t2现在就是一个Salary了,在方法的内部,它拥有了属性BaseSalary和Bonus,代码如下:

    public class SalaryComputer
    {
        public int Compare<T>(T t1, T t2) where T:Salary
        {
            if (t1.BaseSalary > t2.BaseSalary)
            {
                return 1;
            }
            else if (t1.BaseSalary == t2.BaseSalary)
            {
                return 0;
            }
            else
            {
                return -1;
            }
        }
    }

那么可以为泛型参数指定那些约束呢?

1、指定参数是值类型(除Nullable外),可以有如下形式:

        public void Method1<T>(T t) where T : struct
        { 
            
        }

2、指定参数是引用类型,可以有如下形式:

        public void Method1<T>(T t) where T : class
        {

        }

        public void Method1<T>(T t) where T : Salary
        {

        }

注意object不能用来作为约束。

3、指定参数具有无参数的公共构造函数,可以有如下形式:

        public void Method2<T>(T t) where T : new()
        {
 
        }

注意CLR目前只支持无参构造方法约束。

4、指定参数必须是指定的基类、或者派生自指定的基类。

5、指定参数必须是指定的接口、或者实现指定的接口。

6、指定T提供的类型参数必须是为U提供的参数,或者派生自为U提供的参数。

    public class Sample<U>
    {
        public void Method1<T>(T t) where T : U
        { 
            
        }
    }

7、可以对同一类型的参数设置多个约束,并且约束自身可以是泛型类型。

在编程的过程中应该始终考虑为泛型参数设定约束,正像本建议开始的时候所说,约束使泛型成为一个实实在在的“对象”,让它具有了我们想要的行为和属性,而不仅仅是一个object。

 

 

目录
相关文章
|
1月前
|
C# Windows
C#通过代码实现快捷键编辑
C#通过代码实现快捷键编辑
|
1月前
|
C#
24. C# 编程:用户设定敌人初始血值的实现
24. C# 编程:用户设定敌人初始血值的实现
22 0
|
3月前
|
存储 安全 编译器
C# 11.0中的泛型属性:类型安全的新篇章
【1月更文挑战第23天】C# 11.0引入了泛型属性的概念,这一新特性为开发者提供了更高级别的类型安全性和灵活性。本文将详细探讨C# 11.0中泛型属性的工作原理、使用场景以及它们对现有编程模式的改进。通过深入了解泛型属性,开发者将能够编写更加健壮、可维护的代码,并充分利用C#语言的最新发展。
|
3月前
|
开发框架 .NET 编译器
C# 10.0中Lambda表达式的改进:更简洁、更灵活的代码编写体验
【1月更文挑战第21天】随着C#语言的不断发展,Lambda表达式作为一种简洁、高效的函数式编程工具,在C# 10.0中迎来了重要的改进。本文将详细探讨C# 10.0中Lambda表达式的新特性,包括参数类型的推断增强、自然类型的Lambda参数以及Lambda表达式的属性改进等。这些改进不仅简化了Lambda表达式的编写过程,还提升了代码的可读性和灵活性,为开发者带来了更优质的编程体验。
|
3月前
|
存储 编解码 开发工具
Baumer工业相机堡盟工业相机如何通过NEOAPI SDK使用UserSet功能保存和载入相机的各类参数(C#)
Baumer工业相机堡盟工业相机如何通过NEOAPI SDK使用UserSet功能保存和载入相机的各类参数(C#)
37 0
|
1月前
|
开发框架 小程序 .NET
C#动态生成带参数的小程序二维码
C#动态生成带参数的小程序二维码
|
1月前
|
Java C# 开发工具
第一个C#程序
第一个C#程序
12 0
|
1月前
|
存储 安全 Java
34.C#:listT泛型集合
34.C#:listT泛型集合
17 1
|
1月前
|
数据采集 存储 C#
抓取Instagram数据:Fizzler库带您进入C#程序的世界
在当今数字化的世界中,数据是无价之宝。社交媒体平台如Instagram成为了用户分享照片、视频和故事的热门场所。作为开发人员,我们可以利用爬虫技术来抓取这些平台上的数据,进行分析、挖掘和应用。本文将介绍如何使用C#编写一个简单的Instagram爬虫程序,使用Fizzler库来解析HTML页面,同时利用代理IP技术提高采集效率。
抓取Instagram数据:Fizzler库带您进入C#程序的世界
|
1月前
|
开发框架 安全 .NET
C# .NET面试系列三:集合、异常、泛型、LINQ、委托、EF!
<h2>集合、异常、泛型、LINQ、委托、EF! #### 1. IList 接口与 List 的区别是什么? IList 接口和 List 类是C#中集合的两个相关但不同的概念。下面是它们的主要区别: <b>IList 接口</b> IList 接口是C#中定义的一个泛型接口,位于 System.Collections 命名空间。它派生自 ICollection 接口,定义了一个可以通过索引访问的有序集合。 ```c# IList 接口包含一系列索引化的属性和方法,允许按索引访问、插入、移除元素等。 由于是接口,它只定义了成员的契约,而不提供具体的实现。类似于 IEnumera
158 2