百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

面试 | .NET基础知识快速通关(6)

bigegpt 2024-08-23 11:55 2 浏览


此系列文章为我在2015年发布于博客园的.NET基础拾遗系列,它十分适合初中级.NET开发工程师在面试前进行一个系统的复习,因此我将其搬到头条号分享与你。

本文为第六篇,我们会对.NET的集合与泛型相关考点进行基础复习,全文会以Q/A的形式展现,即以面试题的形式来描述。

1 int[]是值类型还是应用类型?

在.NET中的数组类型和C++中区别很大,.NET中无论是存储值类型对象的数组还是存储引用类型的数组,其本身都是引用类型,其内存也都是分配在堆上的。它们的共同特征在于:所有的数组类型都继承自System.Array,而System.Array又实现了多个接口,并且直接继承自System.Object。不同之处则在于存储值类型对象的数组所有的值都已经包含在数组内,而存储引用类型对象的数组,其值则是一个引用,指向位于托管堆中的实例对象。

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

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

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的数组内容转换方式的区别:

public class Program
{
    public 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具有较高的灵活性。

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中定义了一些静态的泛型方法,我们应该在编码实践时充分使用这些泛型容器,以提高我们的开发和系统的运行效率

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
{
    ......      
}

总结

本文总结复习了.NET的集合与泛型处理相关的重要知识点,下一篇会总结.NET中流与序列化处理相关的重要知识点,欢迎继续关注!


参考资料(全是经典)

朱毅,《进入IT企业必读的200个.NET面试题》

张子阳,《.NET之美:.NET关键技术深入解析》

王涛,《你必须知道的.NET》

相关推荐

悠悠万事,吃饭为大(悠悠万事吃饭为大,什么意思)

新媒体编辑:杜岷赵蕾初审:程秀娟审核:汤小俊审签:周星...

高铁扒门事件升级版!婚宴上‘冲喜’老人团:我们抢的是社会资源

凌晨两点改方案时,突然收到婚庆团队发来的视频——胶东某酒店宴会厅,三个穿大红棉袄的中年妇女跟敢死队似的往前冲,眼瞅着就要扑到新娘的高额钻石项链上。要不是门口小伙及时阻拦,这婚礼造型团队熬了三个月的方案...

微服务架构实战:商家管理后台与sso设计,SSO客户端设计

SSO客户端设计下面通过模块merchant-security对SSO客户端安全认证部分的实现进行封装,以便各个接入SSO的客户端应用进行引用。安全认证的项目管理配置SSO客户端安全认证的项目管理使...

还在为 Spring Boot 配置类加载机制困惑?一文为你彻底解惑

在当今微服务架构盛行、项目复杂度不断攀升的开发环境下,SpringBoot作为Java后端开发的主流框架,无疑是我们手中的得力武器。然而,当我们在享受其自动配置带来的便捷时,是否曾被配置类加载...

Seata源码—6.Seata AT模式的数据源代理二

大纲1.Seata的Resource资源接口源码2.Seata数据源连接池代理的实现源码3.Client向Server发起注册RM的源码4.Client向Server注册RM时的交互源码5.数据源连接...

30分钟了解K8S(30分钟了解微积分)

微服务演进方向o面向分布式设计(Distribution):容器、微服务、API驱动的开发;o面向配置设计(Configuration):一个镜像,多个环境配置;o面向韧性设计(Resista...

SpringBoot条件化配置(@Conditional)全面解析与实战指南

一、条件化配置基础概念1.1什么是条件化配置条件化配置是Spring框架提供的一种基于特定条件来决定是否注册Bean或加载配置的机制。在SpringBoot中,这一机制通过@Conditional...

一招解决所有依赖冲突(克服依赖)

背景介绍最近遇到了这样一个问题,我们有一个jar包common-tool,作为基础工具包,被各个项目在引用。突然某一天发现日志很多报错。一看是NoSuchMethodError,意思是Dis...

你读过Mybatis的源码?说说它用到了几种设计模式

学习设计模式时,很多人都有类似的困扰——明明概念背得滚瓜烂熟,一到写代码就完全想不起来怎么用。就像学了一堆游泳技巧,却从没下过水实践,很难真正掌握。其实理解一个知识点,就像看立体模型,单角度观察总...

golang对接阿里云私有Bucket上传图片、授权访问图片

1、为什么要设置私有bucket公共读写:互联网上任何用户都可以对该Bucket内的文件进行访问,并且向该Bucket写入数据。这有可能造成您数据的外泄以及费用激增,若被人恶意写入违法信息还可...

spring中的资源的加载(spring加载原理)

最近在网上看到有人问@ContextConfiguration("classpath:/bean.xml")中除了classpath这种还有其他的写法么,看他的意思是想从本地文件...

Android资源使用(android资源文件)

Android资源管理机制在Android的开发中,需要使用到各式各样的资源,这些资源往往是一些静态资源,比如位图,颜色,布局定义,用户界面使用到的字符串,动画等。这些资源统统放在项目的res/独立子...

如何深度理解mybatis?(如何深度理解康乐服务质量管理的5个维度)

深度自定义mybatis回顾mybatis的操作的核心步骤编写核心类SqlSessionFacotryBuild进行解析配置文件深度分析解析SqlSessionFacotryBuild干的核心工作编写...

@Autowired与@Resource原理知识点详解

springIOCAOP的不多做赘述了,说下IOC:SpringIOC解决的是对象管理和对象依赖的问题,IOC容器可以理解为一个对象工厂,我们都把该对象交给工厂,工厂管理这些对象的创建以及依赖关系...

java的redis连接工具篇(java redis client)

在Java里,有不少用于连接Redis的工具,下面为你介绍一些主流的工具及其特点:JedisJedis是Redis官方推荐的Java连接工具,它提供了全面的Redis命令支持,且...