.NET基础拾遗(3)字符串、集合和流2

二、常用集合和泛型

2.1 int[]是值类型还是引用类型?

  .NET中无论是存储值类型对象的数组还是存储引用类型的数组,其本身都是引用类型,其内存也都是分配在堆上的。所有的数组类型都继承自System.Array,而System.Array又实现了多个接口且直接继承自System.Object。、

不同之处则在于存储值类型对象的数组所有的值都已经包含在数组内,而存储引用类型对象的数组,其值则是一个引用,指向位于托管堆中的实例对象。

  下图直观地展示了二者内存分配的差别(假设object[]中存储都是DateTime类型的对象实例):

  在.NET中CLR会检测所有对数组的访问,任何视图访问数组边界以外的代码都会产生一个IndexOutOfRangeException异常。

2.2 数组之间如何进行转换?

  数组类型的转换需要遵循以下两个原则:

  (1)包含值类型的数组不能被隐式转换成其他任何类型

  (2)两个数组类型能够相互转换的一个前提是两者维数相同

  我们可以通过以下代码来看看数组类型转换的机制:

    // 编译成功
    string[] sz = { "a", "a", "a" };
    object[] oz = sz;
    // 编译失败,值类型的数组不能被转换
    int[] sz2 = { 1, 2, 3 };
    object[] oz2 = sz;
    // 编译失败,两者维数不同
    string[,] sz3 = { { "a", "b" }, { "a", "c" } };
    object[] oz3 = sz3;

  除了类型上的转换,我们平时还可能会遇到内容转换的需求。例如,在一系列的用户界面操作之后,系统的后台可能会得到一个DateTime的数组,而现在的任务则是将它们存储到数据库中,而数据库访问层提供的接口只接受String[]参数,这时我们要做的就是把DateTime[]从内容上转换为String[]对象。当然,惯常做法是遍历整个源数组,逐一地转换每个对象并且将其放入一个目标数组类型容器中,最后再生成目标数组。But,这里我们推荐使用Array.ConvertAll方法,它提供了一个简便的转换数组间内容的接口,我们只需指定源数组的类型、对象数组的类型和具体的转换算法,该方法就能高效地完成转换工作。

  下面的代码清楚地展示了普通的数组内容转换方式和使用Array.ConvertAll的数组内容转换方式的区别:

    class Program
    {
        static void Main(string[] args)
        {
            String[] times ={"2008-1-1",
                            "2008-1-2",
                            "2008-1-3"};

            // 使用不同的方法转换
            DateTime[] result1 = OneByOne(times);
            DateTime[] result2 = ConvertAll(times);

            // 结果是相同的
            Console.WriteLine("手动逐个转换的方法:");
            foreach (DateTime item in result1)
            {
                Console.WriteLine(item.ToString("yyyy-MM-dd"));
            }
            Console.WriteLine("使用Array.Convert方法:");
            foreach (DateTime item2 in result2)
            {
                Console.WriteLine(item2.ToString("yyyy-MM-dd"));
            }

            Console.ReadKey();
        }

        // 逐个手动转换
        private static DateTime[] OneByOne(String[] times)
        {
            List<DateTime> result = new List<DateTime>();
            foreach (String item in times)
            {
                result.Add(DateTime.Parse(item));
            }
            return result.ToArray();
        }

        // 使用Array.ConertAll方法
        private static DateTime[] ConvertAll(String[] times)
        {
            return Array.ConvertAll(times,
                new Converter<String, DateTime>
                (DateTimeToString));
        }

        private static DateTime DateTimeToString(String time)
        {
            return DateTime.Parse(time);
        }
    }

  从上述代码可以看出,二者实现了相同的功能,但是Array.ConvertAll不需要我们手动地遍历数组,也不需要生成一个临时的容器对象,更突出的优势是它可以接受一个动态的算法作为具体的转换逻辑。当然,明眼人一看就知道,它是以一个委托的形式作为参数传入,这样的机制保证了Array.ConvertAll具有较高的灵活性。

2.3 简述泛型的基本原理

  泛型的语法和概念类似于C++中的template(模板),它是.NET 2.0中推出的众多特性中最为重要的一个,方便我们设计更加通用的类型,也避免了容器操作中的装箱和拆箱操作

  假如我们要实现一个排序算法,要求能够针对各种类型进行排序。按照以前的做法,我们需要对int、double、float等类型都实现一次,但是我们发现除了数据类型,其他的处理逻辑完全一致。这时,我们便可以考虑使用泛型来进行实现:

    public static class SortHelper<T> where T : IComparable
    {
        public static void BubbleSort(T[] array)
        {
            int length = array.Length;
            for (int i = 0; i <= length - 2; i++)
            {
                for (int j = length - 1; j >= 1; j--)
                {
                    // 对两个元素进行交换
                    if (array[j].CompareTo(array[j - 1]) < 0)
                    {
                        T temp = array[j];
                        array[j] = array[j - 1];
                        array[j - 1] = temp;
                    }
                }
            }
        }
    }

Tips:Microsoft在产品文档中建议所有的泛型参数名称都以T开头,作为一个中编码的通用规范,建议大家都能遵守这样的规范,类似的规范还有所有的接口都以I开头。

  泛型类型和普通类型有一定的区别,通常泛型类型被称为开放式类型,.NET中规定开放式类型不能实例化,这样也就确保了开放式类型的泛型参数在被指定前,不会被实例化成任何对象(事实上,.NET也没有办法确定到底要分配多少内存给开放式类型)。为开放式的类型提供泛型的实例导致了一个新的封闭类型的生成,但这并不代表新的封闭类型和开放类型有任何继承关系,它们在类结构图上是处于同一层次,并且两者之间没有任何关系。下图展示了这一概念:

  此外,在.NET中的System.Collections.Generic命名空间下提供了诸如List<T>、Dictionary<T>、LinkedList<T>等泛型数据结构,并且在System.Array中定义了一些静态的泛型方法,我们应该在编码实践时充分使用这些泛型容器,以提高我们的开发和系统的运行效率

2.4 泛型的主要约束和次要约束是什么?

  当一个泛型参数没有任何约束时,它可以进行的操作和运算是非常有限的,因为不能对实参进行任何类型上的保证,这时候就需要用到泛型约束。泛型的约束分为:主要约束和次要约束,它们都使实参必须满足一定的规范,C#编译器在编译的过程中可以根据约束来检查所有泛型类型的实参并确保其满足约束条件。

  (1)主要约束

  一个泛型参数至多拥有一个主要约束,主要约束可以是一个引用类型、class或者struct。如果指定一个引用类型(class),那么实参必须是该类型或者该类型的派生类型。相反,struct则规定了实参必须是一个值类型。下面的代码展示了泛型参数主要约束:

    public class ClassT1<T> where T : Exception
    {
        private T myException;
        public ClassT1(T t)
        {
            myException = t;
        }
        public override string ToString()
        {
            // 主要约束保证了myException拥有source成员
            return myException.Source;
        }
    }

    public class ClassT2<T> where T : class
    {
        private T myT;
        public void Clear()
        {
            // T是引用类型,可以置null
            myT = null;
        }
    }

    public class ClassT3<T> where T : struct
    {
        private T myT;
        public override string ToString()
        {
            // T是值类型,不会发生NullReferenceException异常
            return myT.ToString();
        }
    }

  泛型参数有了主要约束后,也就能够在类型中对其进行一定的操作了。

  (2)次要约束

  次要约束主要是指实参实现的接口的限定。对于一个泛型,可以有0到无限的次要约束,次要约束规定了实参必须实现所有的次要约束中规定的接口。次要约束与主要约束的语法基本一致,区别仅在于提供的不是一个引用类型而是一个或多个接口。例如我们为上面代码中的ClassT3增加一个次要约束:

    public class ClassT3<T> where T : struct, IComparable
    {
        ......
    }

时间: 2024-08-10 17:09:04

.NET基础拾遗(3)字符串、集合和流2的相关文章

.NET基础拾遗(3)字符串、集合和流

一.字符串处理 1.1 StringBuilder类型有什么作用? 众所周知,在.NET中String是引用类型,具有不可变性,当一个String对象被修改.插入.连接.截断时,新的String对象就将被分配,这会直接影响到性能.但在实际开发中经常碰到的情况是,一个String对象的最终生成需要经过一个组装的过程,而在这个组装过程中必将会产生很多临时的String对象,而这些String对象将会在堆上分配,需要GC来回收,这些动作都会对程序性能产生巨大的影响.事实上,在String的组装过程中,

.NET基础拾遗(3)字符串、集合和流3

三.流和序列化 3.1 流的概念以及.NET中有哪些常见的流? 流是一种针对字节流的操作,类似于内存与文件之间的一个管道.读取文件的过程就可以看作是字节流的一个过程. 常见的流类型包括:文件流.终端操作流及网络Socket等,在.NET中,System.IO.Stream类型被设计为作为所有流类型的虚基类,当需要自定义一种流类型时也应该直接或者间接地继承自Stream类型.下图展示了在.NET中常见的流类型以及它们的类型结构: 从上图中可以发现,Stream类型继承自MarshalByRefOb

.NET基础拾遗(3)字符串、集合和流1

一.字符串处理 1.1 StringBuilder类型 众所周知,在.NET中String是引用类型,具有不可变性,当一个String对象被修改.插入.连接.截断时,新的String对象就将被分配,这会直接影响到性能.在这个组装过程中必将会产生很多临时的String对象,而这些String对象将会在堆上分配,需要GC来回收. 鉴于此,在.NET中提供了StringBuilder,其设计思想源于构造器(Builder)设计模式,致力于解决复杂对象的构造问题.StringBuilder类型在最终生成

Python基础(四) 基础拾遗、数据类型进阶

一.基础拾遗 (一).变量作用域 外层变量,可以被内层变量直接调用:内层变量,无法被外层变量使用.这种说法在其它语言中适用,在python中除了栈以外,正常的变量作用域,只要执行声明并在内存中存在,该变量就可以在下面的代码中使用. (二).位运算符.三元运算 1,位运算符,请看下面代码 1 128 64 32 16 8 4 2 1 2 0 0 0 0 0 0 0 0 3 4 5 #!/usr/bin/env python 6 7 a=60 #00111100 (将十进制转换成二进制,进行位运算)

基础拾遗------redis详解

基础拾遗 基础拾遗------redis详解 基础拾遗------反射详解 基础拾遗------委托详解 基础拾遗------接口详解 基础拾遗------泛型详解 前言 这篇文章和以往的基础拾遗有所不同,以前的介绍的都是c#基础,今天介绍的是redis.因为项目中一只在使用,我想现在大部分项目中都会用到nosql,缓存,今天就介绍一下redis..废话少说下面开始正题. 1.redis是什么? Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库. 对的redi

Sentinel 发布里程碑版本,添加集群流控功能

自去年10月底发布GA版本后,Sentinel在近期发布了另一个里程碑版本v1.4(最新的版本号是v1.4.1),加入了开发者关注的集群流控功能. 集群流控简介 为什么要使用集群流控呢?假设我们希望给某个用户限制调用某个 API 的总 QPS 为 50,但机器数可能很多(比如有 100 台).这时候我们很自然地就想到,找一个 server 来专门来统计总的调用量,其它的实例都与这台 server 通信来判断是否可以调用.这就是最基础的集群流控的方式. 那么这个 server 如何部署呢?最直观的

【C++基础】 指针&amp;字符串&amp;数组

先贴代码,总结以后再写,和5中内存分配方式密切相关 PS: str 返回整个字符串,str[0],*str返回首字符h char *strA(){ char str[]="hello!"; //局部数组,局部变量, str存在栈区 return str; //局部变量不能传值,估计会成为野指针 //函数返回局部变量的地址,当被调用完成时,str就释放了,因此返回结果是不确定的且不安全的 } char *strA2(){ char *str = "hello2!";/

面向对象基础(class0425)字符串与集合

常用类库 学习.net就是学习它的无数个类库怎么用,先看两个简单的 String 字符串,不可变特性.字符串可以看成字符数组 属性 Length 方法 IsNullOrEmpty() 静态方法 ToCharArray() ToLower() string不可变性 ToUpper() Equals() 忽略大小写的比较 Join() 静态方法 Format () 静态方法 IndexOf() LastIndexOf() Substring() Split() Replace() Trim()  C

Ado.Net基础拾遗二:插入,更新,删除数据

插入数据 1 public void InsertDataToSQL() 2 { 3 string conStr = ConfigurationManager.ConnectionStrings["NorthwindConnectionString"].ConnectionString; 4 SqlConnection conn = new SqlConnection(conStr); 5 conn.Open(); 6 7 SqlCommand cmd = new SqlCommand