c# 泛型

本文介绍如何使用泛型创建链表,以及在委托和接口中使用协变与逆变的概念。探讨了不同类型的约束,包括主要约束、次要约束及构造器约束。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

封闭类型才能创建实例,开放类型不能创建实例;

每个封闭类型都有自己的静态字段;

 

通过泛型实现一个链表:

    //链表基类
    internal class Node
    {
        protected Node m_next;
        public Node(Node next)
        {
            this.m_next = next;
        }
    }
    //链表派生类
    internal class NodeType<T>: Node
    {
        public T m_data;
        public NodeType(T data): this(data, null)
        {

        }

        public NodeType(T data, Node next) : base(next)
        {
            this.m_data = data;
        }

        public override string ToString()
        {
            return this.m_data.ToString() + ((this.m_next != null) ? this.m_next.ToString() : String.Empty);
        }
    }

 

委托和接口泛型中的逆变和协变

不变量: 意味着泛型类型参数不能更改。

逆变量: 意味着泛型类型参数可以从一个类更改为它的某个派生类。在C#是用in关键字标记逆变量形式的泛型类型参数。逆变量泛型类型参数只出现在输入位置,比如作为方法的参数。

协变量: 意味着泛型类型参数可以从一个类更改为它的某个基类。C#是用out关键字标记协变量形式的泛型类型参数。协变量泛型类型参数只能出现在输出位置,比如作为方法的返回类型。

例如:

            Func<Object, ArgumentException> fn1 = null;
            Func<string, Exception> fn2 = fn1;

        是能够成立的。因为fn1变量引用一个方法,获取一个Object,返回一个ArgumentException。而fn2变量引用另一个方法,获取一个String,返回一个Exception。由于可将一个String传给期待Object的方法(因为String从Object派生),而且由于可以获取返回ArgumentException的一个方法的结构,并将这个结构当成一个Exception(因为Exception 是 ArgumentException 的基类),所以上述代码能正常编译。

 

可验证性和约束

        private static T Min<T>(T o1, T o2) where T : IComparable<T>
        {
            if (o1.CompareTo(o2) < 0)
            {
                return o1;
            }
            return o2;
        }

        where关键字告诉编译器,为T指定的任何类型都必须实现同类型(T)的泛型IComparable<T>接口。

        约束可用于泛型类型的类型参数,也可应用于泛型方法的类型参数。(注: CLR不允许基于类型参数名称或约束来进行重载; 只能基于类型参数个数对类型或方法进行重载)

          重写虚泛型方法时,重写的方法必须指定相同数量的类型参数,而且这些类型参数会继承在基类方法上指定的约束。事实上,根本不允许为重写方法的类型参数指定任何约束。但是类型参数的名称是可以改变的。 实现接口方法时类似。

 

主要约束

        类型参数可以指定零个或一个主要约束。主要约束可以是代表非密封类的一个引用类型。(不能指定以下特殊引用类型: System.Object, System.Array, System.Delegate, System.MulticastDelegate, System.ValueType, System,Enum 或者 System.Void)。

        指定引用类型约束时,相当于向编译器承诺: 一个指定的类型实参要么是与约束类型相同的类型,要么是从约束类型派生的类型。

 

次要约束

        类型参数可以指定零个或多个次要约束,次要约束代表接口类型。这种约束向编译器承诺类型实参实现了接口。

        还有一种次要约束称为类型参数约束,有时也称为裸类型约束。它允许一个泛型类型或方法规定: 指定的类型实参要么就是约束的类型,要么是约束的类型的派生类。如下:

        private static List<TBase> ConvertIList<T, TBase>(IList<T> list) where T: TBase
        {
            List<TBase> baseList = new List<TBase>(list.Count);
            for (int index = 0; index < list.Count; index++)
            {
                baseList.Add(list[index]);
            }
            return baseList;
        } 

 

构造器约束

        类型参数可指定零个或一个构造器约束, 它向编译器承诺类型实参是实现了公共无参构造器的非抽象类型。(注: 如果同时使用构造器约束和struct约束,C#编译器会认为这是一个错误,因为这是多余的。)

    internal class ConstructorConstraint<T> where T : new()
    {
        public static T Factory()
        {
            //允许,因为所有值类型都隐式有一个公共无参构造器。
            //如果指定的是引用类型,约束也要求它提供公共无参构造器
            return new T();
        }
    }

 

 

 

### C# 使用指南与示例详解 #### 1. **什么是?** 是一种强大的编程特性,允许开发者编写能够处理多种数据类的代码而无需重复实现。通过引入类参数,可以在编译时提供更强的类安全性并优化性能[^1]。 #### 2. **基本语法与定义** ##### 定义类 下面是一个简单的类定义: ```csharp using System; namespace Fountain.Net.Base.Sample { public class GenericClass<T> { public T GenericField { get; set; } /// <summary> /// 方法:带有<>和类参数的方法 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="message"></param> public void Message<T>(T message) { Console.WriteLine("GenericField值={0}", GenericField); Console.WriteLine("GenericField值类是= {0}", GenericField?.GetType()?.FullName ?? "null"); Console.WriteLine(); Console.WriteLine("方法参数值={0}", message); Console.WriteLine("方法参数类是={0}", message?.GetType()?.FullName ?? "null"); } } } ``` 此代码片段展示了一个名为 `GenericClass` 的类,其中包含一个属性 `GenericField` 和一个方法 `Message`[^1]。 ##### 使用类 在主程序中实例化并使用上述类如下所示: ```csharp using System; namespace Fountain.Net.Base.Sample { internal class Program { static void Main(string[] args) { // 实例化类 GenericClass<string> generic = new GenericClass<string>(); generic.GenericField = "A string"; // 调用方法 generic.Message<int>(6); Console.ReadLine(); } } } ``` 这段代码演示了如何创建一个指定类类实例,并调用了其内部的一个方法。 #### 3. **中的运算符支持** C# 提供了一种机制使得即使是在不知道确切的数据类的情况下也可以执行诸如加减乘除之类的操作。这通常涉及将类转换为动态类 `(dynamic)` 来绕过静态类检查: ```csharp public class MathOperations<T> { public T Add(T a, T b) { return (dynamic)a + (dynamic)b; } public T Subtract(T a, T b) { return (dynamic)a - (dynamic)b; } } ``` 这种方法虽然灵活但可能带来运行期异常的风险,应谨慎使用[^2]。 #### 4. **约束** 有时我们需要对可以作为类参数传递给的实际类施加一定的限制条件。例如,如果希望确保某个类具有默认构造函数,则可采用以下方式: ```csharp public class MyGeneric3<T> where T : new() { public MyGeneric3(T t) { Console.WriteLine($"result:type={t.GetType().Name};value={t}"); } } ``` 在此处添加了 `where T : new()` 子句表明只有那些拥有公共无参构造器的类才适合作为此处的 `T` 类参数[^3]。 还有更多种类别的约束可用,比如限定到特定基类或者接口继承关系等等[^5]。 #### 5. **内置委托** .NET Framework 已经预先定义了一些常用的委托类来帮助简化开发工作流。主要包括但不限于以下几个类别: - **Action**: 表达不返回任何结果的动作; - **Func**: 返回单一结果的功能表达式; - **Predicate**: 特殊形式 Func<bool>, 主要用于过滤逻辑判断场景下。 举个简单例子说明 Action 的用途: ```csharp // 创建匿名方法并通过 action 委托绑定 Action<string> printString = s => Console.WriteLine(s); printString("Hello World!"); ``` 以上就是关于 C# 的一些基础介绍及其高级特性的讲解[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值