LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

C# 泛型的概念

admin
2025年3月22日 7:41 本文热度 135

C# 中的泛型(Generics),这是一种允许程序员创建灵活、可重用的代码的功能。我们将了解泛型的基本概念、为什么要使用泛型、如何定义和使用泛型类和方法,以及泛型的一些高级特性。本课程将通过丰富的示例帮助您掌握泛型的使用,简单理解可以是一种模板。

泛型的基本概念

泛型允许我们创建不指定具体数据类型的类、接口、方法和委托。通过使用泛型,我们可以编写更加灵活和可重用的代码。

为什么使用泛型? 

  • 类型安全
    :泛型提供了编译时类型检查,减少了运行时错误。
  • 性能
    :使用泛型可以避免装箱(boxing)和拆箱(unboxing)操作,提升性能。
  • 代码重用
    :可以使用同一个类或方法处理不同的数据类型。

示例:不使用泛型的类 

namespace AppGenerics
{
    publicclass Box
    {

        private object content;

        public void SetContent(object content)
        
{
            this.content = content;
        }

        public object GetContent()
        
{
            return content;
        }
    }


    internal class Program
    {

        static void Main(string[] args)
        
{
            Box box = new Box();
            box.SetContent(42); // 装箱操作
            int number = (int)box.GetContent(); // 需要显式拆箱
            Console.WriteLine(number);
            Console.ReadKey();
        }
    }
}

在上面的例子中,Box 类使用 object 类型来存储内容,这意味着它可以接受任何类型的数据。然而,这会导致装箱和拆箱操作,以及潜在的运行时类型错误。

示例:使用泛型的类 

namespace AppGenerics
{
    publicclass Box<T>
    {

        private T content;

        public void SetContent(T content)
        
{
            this.content = content;
        }

        public T GetContent()
        
{
            return content;
        }
    }


    internal class Program
    {

        static void Main(string[] args)
        
{
            Box<int> intBox = new Box<int>();
            intBox.SetContent(42); // 无需装箱操作
            int number = intBox.GetContent(); // 无需拆箱,类型安全
            Console.WriteLine(number);
            Console.ReadKey();
        }
    }
}

在这个例子中,我们定义了一个泛型类 Box<T>,其中 T 是一个占位符,代表将来会被实际的数据类型替换。当我们创建 Box<int> 实例时,T 被替换为 int 类型,这样就避免了装箱和拆箱操作,并且保证了类型安全。

定义和使用泛型方法

泛型也可以用于方法。泛型方法可以在非泛型类中定义,也可以在泛型类中定义。

示例:泛型方法 

namespace AppGenerics
{
    publicclass Utils
    {

        publicstatic T Max<T>(T a, T b) where T : IComparable<T>
        {
            return a.CompareTo(b) > 0 ? a : b;
        }
    }


    internal class Program
    {

        static void Main(string[] args)
        
{
            int maxInt = Utils.Max(1020); // 调用泛型方法
            string maxString = Utils.Max("apple""orange"); // 调用泛型方法
            Console.WriteLine("Max int: " + maxInt);
            Console.ReadKey();
        }
    }
}

在这个例子中,我们定义了一个泛型方法 Max,它接受两个参数并返回较大的一个。我们使用了泛型约束 where T : IComparable<T>,这表示 T 必须实现 IComparable<T> 接口,这样我们就可以调用 CompareTo 方法。

泛型约束

泛型约束用于指定泛型类型参数必须满足的条件。常见的泛型约束包括:

  • where T : struct
    T 必须是值类型。
  • where T : class
    T 必须是引用类型。
  • where T : new()
    T 必须有一个无参数的构造函数。
  • where T : baseClass
    T 必须是指定基类或其派生类。
  • where T : interface
    T 必须实现指定的接口。

示例:带约束的泛型类 

namespace AppGenerics
{
    publicclass NullableValue<T> where T :struct
    {

        private T? value;

        public NullableValue(T? value)
        
{
            this.value = value;
        }

        publicbool HasValue => value.HasValue;

        public T GetValueOrDefault() => value.GetValueOrDefault();
    }


    internal class Program
    {

        static void Main(string[] args)
        
{

            NullableValue<int> nullableInt = new NullableValue<int>(null);
            bool hasValue = nullableInt.HasValue; // false
            int defaultValue = nullableInt.GetValueOrDefault(); // 0
            Console.WriteLine($"HasValue: {hasValue}, DefaultValue: {defaultValue}");
            Console.ReadKey();
        }
    }
}

在这个例子中,我们定义了一个泛型类 NullableValue<T>,它使用了约束 where T : struct,这意味着 T 必须是一个值类型。这个类允许我们创建一个可以为 null 的值类型的封装,类似于 .NET 中的 Nullable<T>

泛型接口和泛型委托

泛型也可以用于接口和委托的定义,使它们能够以类型安全的方式处理多种数据类型。

示例:泛型接口 

public class Product
{

    publicint Id { get; set; }
    publicstring Name { get; set; }
    public decimal Price { get; set; }
}
public interface IRepository<T>
{
    void Add(T item);
    GetById(int id);
    IEnumerable<T> GetAll();
}

publicclass ProductRepository : IRepository<Product>
{
    public void Add(Product item) 

    }
    public Product GetById(int id) 
        returnnew Product();
    }
    public IEnumerable<Product> GetAll() { 
        returnnew List<Product>();
    }
}

在这个例子中,我们定义了一个泛型接口 IRepository<T>,它提供了对任何类型 T 的通用存储库操作。然后我们实现了一个具体的 ProductRepository 类,它实现了 IRepository<Product> 接口,提供了对 Product 类型的操作。

示例:泛型委托 

namespace AppGenerics
{
    public delegate T Transformer<T>(T input);

    publicclass MathOperations
    {

        public static int Square(int number) => number * number;
    }

    internal class Program
    {

        static void Main(string[] args)
        
{
            Transformer<int> squareTransformer = MathOperations.Square;
            int result = squareTransformer(5); // 结果为 25
            Console.WriteLine(result);
            Console.ReadKey();
        }
    }
}

在这个例子中,我们定义了一个泛型委托 Transformer<T>,它可以引用任何接受一个 T 类型参数并返回 T 类型结果的方法。然后我们创建了一个 squareTransformer 委托实例,并将其关联到 MathOperations.Square 方法。

泛型在集合中的应用

泛型在 .NET 集合类中广泛使用,例如 List<T>Dictionary<TKey, TValue>HashSet<T> 等。

示例:泛型集合 

namespace AppGenerics
{

    internal class Program
    {

        static void Main(string[] args)
        
{
            List<string> names = new List<string>();
            names.Add("Alice");
            names.Add("Bob");

            Dictionary<intstring> userDictionary = new Dictionary<intstring>();
            userDictionary.Add(1"Alice");
            userDictionary.Add(2"Bob");

            HashSet<int> uniqueNumbers = new HashSet<int>();
            uniqueNumbers.Add(1);
            uniqueNumbers.Add(2);
            uniqueNumbers.Add(1); // 不会被加入集合
            Console.ReadKey();
        }
    }
}

在这些例子中,我们创建了几种不同类型的泛型集合,并对它们进行了操作。泛型集合提供了类型安全的数据存储和访问,并且可以减少需要进行的类型转换,从而提高性能。

泛型方法

泛型也可以用于方法,允许在方法级别定义类型参数。这使得我们可以编写能够处理不同类型数据的灵活方法。

示例:泛型方法 

namespace AppGenerics
{
    publicstaticclass GenericUtilities
    {

        publicstatic T Max<T>(T a, T b) where T : IComparable<T>
        {
            return a.CompareTo(b) > 0 ? a : b;
        }

        publicstatic T Min<T>(T a, T b) where T : IComparable<T>
        {
            return a.CompareTo(b) < 0 ? a : b;
        }

        publicstatic T Max<T>(params T[] values) where T : IComparable<T>
        {
            if (values == null || values.Length == 0)
                thrownew ArgumentException("必须提供至少一个值");

            T max = values[0];
            for (int i = 1; i < values.Length; i++)
            {
                if (values[i].CompareTo(max) > 0)
                    max = values[i];
            }
            return max;
        }
    }
    internal class Program
    {

        static void Main(string[] args)
        
{
            int maxNumber = GenericUtilities.Max(5103815); 
            string longest = GenericUtilities.Max("apple""banana""kiwi""strawberry"); 
            Console.WriteLine($"最大值:{maxNumber}");
            Console.WriteLine($"最长的字符串:{longest}");
            Console.ReadKey();
        }
    }
}

泛型约束的组合

泛型约束可以组合使用,以满足更复杂的要求。

示例:组合泛型约束 

namespace AppGenerics
{
    publicclass Factory<T> where T :classnew()
    {

        public T CreateInstance()
        
{
            returnnew T();
        }
    }
    publicclass MyClass
    {

        public MyClass() /* 构造函数 */ }
    }

    internal class Program
    {

        static void Main(string[] args)
        
{
            Factory<MyClass> factory = new Factory<MyClass>();
            MyClass myClassInstance = factory.CreateInstance();
            Console.ReadKey();
        }
    }
}

在这个例子中,我们定义了一个泛型类 Factory<T>,它使用了组合约束 where T : class, new(),这意味着 T 必须是一个引用类型,并且必须有一个无参数的构造函数。这样,Factory<T> 类的 CreateInstance 方法就可以创建 T 类型的新实例。

泛型的协变和逆变

在 C# 中,泛型接口和泛型委托可以指定协变和逆变。

  • 协变
    (Covariance)允许方法返回比声明的返回类型更具体的类型。
  • 逆变
    (Contravariance)允许方法接受比声明的参数类型更通用的类型。

示例:泛型协变 

namespace AppGenerics
{
    // 定义协变接口 - 注意"out"关键字  
    public interface IProducer<out T>
    {
        Produce()// T只用于返回值位置  
    }

    // 定义水果层次结构  
    publicclass Fruit { }
    publicclass Apple : Fruit { }
    publicclass Gala : Apple { }

    // 实现基本的生产者类  
    publicclass FruitProducer : IProducer<Fruit>
    {
        public Fruit Produce() => new Fruit();
    }

    publicclass AppleProducer : IProducer<Apple>
    {
        public Apple Produce() => new Apple();
    }

    publicclass GalaProducer : IProducer<Gala>
    {
        public Gala Produce() => new Gala();
    }
    internal class Program
    {

        static void Main(string[] args)
        
{
            // 1. 最基本的协变示例  
            IProducer<Apple> appleProducer = new AppleProducer();
            IProducer<Fruit> fruitProducer = appleProducer; // 可以将Apple生产者视为Fruit生产者  

            // 2. 更复杂的示例  
            IProducer<Gala> galaProducer1 = new GalaProducer();
            IProducer<Apple> appleProducer2 = galaProducer1; // Gala生产者可以视为Apple生产者  
            IProducer<Fruit> fruitProducer2 = galaProducer1; // Gala生产者也可以视为Fruit生产者  

            Fruit f1 = fruitProducer.Produce();
            Fruit f2 = appleProducer2.Produce();
            Fruit f3 = fruitProducer2.Produce();

            Console.WriteLine($"fruitProducer 实际产生: {f1.GetType().Name}");
            Console.WriteLine($"appleProducer2 实际产生: {f2.GetType().Name}");
            Console.WriteLine($"fruitProducer2 实际产生: {f3.GetType().Name}");

            Console.ReadKey();

        }
    }
}

在这个例子中,IProducer<out T> 接口声明了一个协变的泛型参数 T(使用 out 关键字)。 重点在于生产者的变化。

示例:泛型逆变 

namespace AppGenerics
{
    public interface IConsumer<in T>
    {
        void Consume(T item);
    }

    publicclass Apple { }
    publicclass Gala : Apple { }

    publicclass AppleConsumer : IConsumer<Apple>
    {
        public void Consume(Apple item) {
            Console.WriteLine("正在消费苹果...");
        }
    }

    internal class Program
    {

        static void Main(string[] args)
        
{

            IConsumer<Apple> appleConsumer = new AppleConsumer();
            IConsumer<Gala> galaConsumer = appleConsumer; // 逆变允许这样做
            galaConsumer.Consume(new Gala());
            appleConsumer.Consume(new Apple());
            Console.ReadKey();

        }
    }
}

在这个例子中,IConsumer<in T> 接口声明了一个逆变的泛型参数 T(使用 in 关键字)。这意味着我们可以将 IConsumer<Apple> 类型的对象赋值给 IConsumer<Gala> 类型的变量,因为 Apple 是 Gala 的基类。

泛型实际应用示例

通用缓存管理器 

namespace AppGenerics
{
    publicclass CacheManager<T>
    {

        private readonly Dictionary<string, (T Value, DateTime ExpiryTime)> _cache = new();
        private readonly TimeSpan _defaultExpiry = TimeSpan.FromMinutes(30);

        public void Set(string key, T value, TimeSpan? expiryTime = null)
        
{
            var expiry = DateTime.Now + (expiryTime ?? _defaultExpiry);
            _cache[key] = (value, expiry);
        }

        public bool TryGet(string key, out T value)
        
{
            value = default;
            if (!_cache.TryGetValue(key, out var cacheItem))
                returnfalse;

            if (DateTime.Now > cacheItem.ExpiryTime)
            {
                _cache.Remove(key);
                returnfalse;
            }

            value = cacheItem.Value;
            returntrue;
        }
    }

    // 使用示例
    publicclass UserProfile
    {

        publicint Id { get; set; }
        publicstring Name { get; set; }
    }

    internal class Program
    {

        static void Main(string[] args)
        
{

            // 创建缓存管理器实例
            var userCache = new CacheManager<UserProfile>();

            // 缓存用户信息
            userCache.Set("user:123"new UserProfile { Id = 123, Name = "John" });

            // 获取缓存的用户信息
            if (userCache.TryGet("user:123", out var user))
            {
                Console.WriteLine($"Found user: {user.Name}");
            }
            Console.ReadKey();

        }
    }
}

通用工厂模式 

namespace AppGenerics
{
    public interface IFactory<T>
    {
        Create();
    }

    publicclass ConfigurableFactory<T> : IFactory<T> where T : class
    {

        private readonly Func<T> _creator;

        public ConfigurableFactory(Func<T> creator)
        
{
            _creator = creator;
        }

        public T Create()
        
{
            return _creator();
        }
    }

    // 使用示例
    public interface ILogger
    {
        void Log(string message);
    }

    publicclass ConsoleLogger : ILogger
    {
        public void Log(string message)
        
{
            Console.WriteLine($"[{DateTime.Now}] {message}");
        }
    }

    publicclass FileLogger : ILogger
    {
        private readonly string _path;

        public FileLogger(string path)
        
{
            _path = path;
        }

        public void Log(string message)
        
{
            File.AppendAllText(_path, $"[{DateTime.Now}] {message}\n");
        }
    }

    internal class Program
    {

        static void Main(string[] args)
        
{

            // 创建工厂实例
            var consoleLoggerFactory = new ConfigurableFactory<ILogger>(() => new ConsoleLogger());
            var fileLoggerFactory = new ConfigurableFactory<ILogger>(() => new FileLogger("app.log"));

            // 使用工厂创建实例
            ILogger logger1 = consoleLoggerFactory.Create();
            ILogger logger2 = fileLoggerFactory.Create();

            logger1.Log("Hello, World!");
            logger2.Log("Hello, World!");
            Console.ReadKey();

        }
    }
}

总结

泛型是 C# 中非常强大的特性,它为开发者提供了编写灵活、可重用和类型安全代码的能力。通过使用泛型约束,开发者可以确保泛型类型和方法的正确性和安全性。泛型的协变和逆变进一步增加了泛型类型的灵活性,使得代码更加通用和适应不同的场景。


阅读原文:原文链接


该文章在 2025/3/24 13:20:15 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved