第四单元 泛型

这篇具有很好参考价值的文章主要介绍了第四单元 泛型。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 什么是泛型

编写一个方法,实现两数相加并返回结果。

第四单元 泛型

 文章来源地址https://www.toymoban.com/news/detail-480784.html

作用

  1. 泛型增强了代码的可读性

  2. 泛型有助于实现代码的重用、保护类型的安全以及提高性能。

  3. 我们可以创建泛型集合类。

  4. 泛型实现了类型和方法的参数化

  5. 我们还可以对泛型类进行约束以访问特定数据类型的方法。

  6. 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。

 

定义

泛型是可以当作任意一种且由编译期间决定其最终类型的数据类型。通俗来讲,泛型,即泛指某种类型。

 

2. 泛型类

1. 泛型类声明格式

泛型类,将指定类型参数(Type Parameter,通常以T 表示),紧随类名,并包含在<>符号内。

public class 泛型类<T>
{
    /// <summary>
    /// 泛型属性
    /// </summary>
    public T ItemName { get; set; }

    public string MyName { get; set; } // 也可定义其他的属性
    
}

使用泛型类

泛型类<string> obj = new();
obj.ItemName = "任我行码农场";

Console.WriteLine("ItemName的值是:"+obj.ItemName);
Console.WriteLine("ItemName的类型是:"+obj.ItemName.GetType());

输出结果:

ItemName的值是:任我行码农场
ItemName的类型是:System.String

3. 泛型方法

泛型方法,将指定类型参数(Type Parameter,通常以T 表示),紧随方法名,并包含在<>符号内。

格式

访问修饰符  方法返回类型   方法名<T>(参数列表)
{
	// 方法体...
}

  

普通类中的泛型

public class MyClass
{
    // 泛型方法
    public T Sum<T>(T a, T b)
    {
        return (dynamic) a + b;
    }
}

  

泛型类中的泛型方法

public class 泛型类<T>
{
    /// <summary>
    /// 泛型属性
    /// </summary>
    public T ItemName { get; set; }

    public string MyName { get; set; }

    
    public void Sum<T>(T a, int b)
    {
        Console.WriteLine((dynamic)a+b);
    }
    
}

 

4. 泛型约束

1. 为什么要用泛型约束

[Test]
public void Test2()
{
    MyClass my = new MyClass();
    Student s1 = new Student(1,"张三");
    Student s2 = new Student(2,"李四");
    my.Sum<Student>(s1, s2); // 合适吗?
}

record Student(int Id,string Name);

 

上述代码一定会报错, 两个Student对象不可能可以直接相加!!

此时,如果不对Sum 这个泛型方法加以约束,就很有可能出现上述情况。

所谓泛型约束,实际上就是约束的类型T。使T必须遵循一定的规则。比如T必须继承自某个类或者T必须实现某个接口等。 使用where关键字加上约束

格式如下:

public class 泛型类<T> where T:约束类型
{
   
}

  

2. 约束的类型

struct 类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型
class 类型参数必须是引用类型,包括任何类、接口、委托或数组类型。
new() 类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。
基类名 类型参数必须是指定的基类或派生自指定的基类
接口名 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。

 

泛型约束--struct

泛型约束中的struct 指定类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型

public class MyClass
{
    // 泛型方法
    public T Sum<T>(T a, T b) where T:struct
    {
        return (dynamic) a + b;
    }
}

[Test]
public void Test2()
{
    MyClass my = new MyClass();
    Student s1 = new Student(1,"张三");
    Student s2 = new Student(2,"李四");
    my.Sum<Student>(s1, s2); // 此时编译器直接给出错误提示,编译失败
}

record Student(int Id,string Name);

 

my.Sum<Student>(s1, s2); // 此时编译器直接给出错误提示,编译失败

 

泛型约束--class

泛型约束class ,指定类型参数必须是引用类型,包括任何类、接口、委托或数组类型。

public interface IRepository<T> where T:class
{
    // 接口也可以有默认实现
    int Add(T model)
    {
        Console.WriteLine("添加了一条数据");
        return 1;
    }

    int Update(T model);

    int Delete(dynamic id);

    T GetModel(dynamic id);

    IList<T> GetList(string condition);
}

 

如果有组合约束时,class约束必须放在最前面。

public interface IRepository<T> where T:class,new() // class放前面,否则编译失败
{
    int Add(T model);

    int Update(T model);

    int Delete(dynamic id);

    T GetModel(dynamic id);

    IList<T> GetList(string condition);
}

 

测试效果

IRepository<int> repository = new IRepository<int>(); // 编译失败

IRepository<object> repository = new IRepository<object>(); // 编译通过

 

泛型约束—new()

泛型约束new(),指定类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。加上该约束后可以在类中或者方法中实例化T类型的对象。

public class BaseDAL<T> where T:class,new() //new()放后面
{
    public List<T> GetList<T>()
    {
        List<T> list = new();
        T t = new(); // 可以实例化了
        list.Add(t);
        
        return list;
    }
}

 

测试效果

BaseDAL<Student> dal = new BaseDAL<Student>(); // 编译失败,Student并未提供无参构造

record Student(int Id,string Name);

  

泛型约束—基类名

类型约束之基类名称,类型参数必须是指定的基类或派生自指定的基类

public class StudentDal<T> where T:BaseModel
{
    
}

class BaseModel
{
    
}

  

说明:基类约束时,基类不能是密封类,即不能是sealed类。sealed类表示该类不能被继承,在这里用作约束就无任何意义,因为sealed类没有子类.

 

泛型约束—接口名称

泛型约束之接口名称,类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。

interface IAnimal
{
    // ...
}

interface IPerson
{
    // ...
}


class 泛型类<T> where T:IAnimal,IPerson
{
    
}

class Student:IAnimal,IPerson
{
    
}

// 测试使用
泛型类<Student> myClass = new 泛型类<Student>(); // 测试通过

  

5. 泛型协变和逆变

协变(Convariant)和逆变(Contravariant)的出现,使数组、委托、泛型类型的隐式转换变得可能。 子类转换成基类,称之为协变;基类转换成子类,称之为逆变。.NET4.0以来,支持了泛型接口的协变和逆变。

 

泛型协变

如果子类泛型隐式转换成基类泛型,使用泛型协变

 

  1. 先准备好两个子父类

    public class Animal
    {
        public virtual void Run()
        {
            Console.WriteLine("动物在跑");
        }
    }
    
    public class Dog:Animal
    {
        public override void Run()
        {
            Console.WriteLine("狗在跑");
        }
    }

     

  2. 定义好泛型协变接口,

    public interface IFactory<out T> // out 协变 只能应用于interface
    {
        T Create(); 
    }
    
    
  3. 实现协变接口
public class FactoryImpl<T>:IFactory<T> where T:new()
{
    public T Create()
    {
        return new T();
    }
}

 

 

 

  1. 测试泛型协变

    [Test]
    public void Test3()
    {
        IFactory<Dog> iFactory = new FactoryImpl<Dog>();
        IFactory<Animal> parentFactory = iFactory; // 协变
    
        Animal animal = parentFactory.Create();
        animal.Run();// 输出结果:狗在跑
    }
  • 泛型接口中的out关键字必不可少

  • out 协变 只能应用于interface

 

泛型逆变

如果基类泛型隐式转换成子类泛型,使用泛型逆变。

 

  1. 关于通知的一个接口

    public interface INotification
    {
        public string Message { get; }
    }
    
    // 关于通知接口的抽象实现。
    public abstract class Notification : INotification
    {
        public abstract string Message { get; }
    }
  2. 关于通知抽象类的具体实现。

    public class MainNotification : Notification
    {
        public override string Message => "您有一封新的邮件";
    }
  3. 接下来,需要把通知的信息发布出去,需要一个发布通知的接口INotifier,该接口依赖INotification,大致INotifier<INotification>,而最终显示通知,我们希望INotifier<MailNotification>,INotifier<INotification>转换成INotifier<MailNotification>,这是逆变,需要关键字in。

    public interface INotifier<in T> where T : INotification
    {
        void Notifier(T notification);
    }

     

  4. 实现INotifier:

    public class Notifier<T> : INotifier<T> where T : INotification
    {
        public void Notify(T notification)
        {
            Console.WriteLine(notification.Message);
        }
    }

     

  5. 客户端调用

    [Test]
    public void Test4()
    {
        INotifier<INotification> notifier = new Notifier<INotification>();
        INotifier<MainNotification> mailNotifier = notifier; // 逆变
        mailNotifier.Notify(new MainNotification());
    }

     

● INotifier的方法Notify()的参数类型是INotification,逆变后把INotification类型参数隐式转换成了实现类 MailNotificaiton。 ● 泛型接口中的in关键字必不可少

 

协变逆变总结

逆变与协变只能放在泛型接口和泛型委托的泛型参数里面,在泛型中out修饰泛型称为协变,协变(covariant) 修饰返回值 ,协变的原理是把子类指向父类的关系,拿到泛型中。

在泛型中in 修饰泛型称为逆变, 逆变(contravariant )修饰传入参数,逆变的原理是把父类指向子类的关系,拿到泛型中。

 

内置的协变逆变泛型

序号 类型 名称
1 接口 IEnumerable<out T>
2 委托 Action<in T>
3 委托 Func<out T>
4 接口 IReadOnlyList<out T>
5 接口 IReadOnlyCollection<out T>

 

6. 泛型的应用

手写ORM框架

ORM 框架,对象关系映射。

  1. 从 老师提供utils 文件夹中将DbHelper拷贝至当前你的项目中

  2. nuget引用:1:Microsoft.Extensions.Configuration,2:System.Data.SqlClient

  3. 封装ORM 框架

    public class DbContext<T> where T : class, new()
    {
         /// <summary>
        /// 添加功能
        /// </summary>
        /// <param name="t">要添加的对象</param>
        public void Add(T t)
        {
            // insert into Student(.属性1,属性2,属性3..) values(.'属性值1','属性值2','属性值3'..)
            StringBuilder sql = new StringBuilder($"insert into {typeof(T).Name}(");
            // 跳过第一个属性
            var propertyInfos = typeof(T).GetProperties().Skip(1);
            var propNames = propertyInfos.Select(p => p.Name).ToList();
            sql.Append(string.Join(",", propNames));
            sql.Append(") values('");
    
            List<string> values = new List<string>();
            foreach (var propertyInfo in propertyInfos)
            {
                // 获取属性值
                values.Add(propertyInfo.GetValue(t).ToString());
            }
            sql.Append(string.Join("','", values));
            sql.Append("')");
            DbHelper.ExecuteNonQuery(sql.ToString());
        }
    
        public List<T> GetList()
        {
            return DbHelper.GetList<T>($"select * from {typeof(T).Name}");
        }
    
    
        public T GetModel(dynamic id)
        {
            var pk = GetPrimaryKey().Name; //获取主键的名称
            //获取一条记录
            return DbHelper.GetList<T>(
                $"select * from {typeof(T).Name} where {pk}=@id",
                new SqlParameter(pk, id)).First();
        }
    
        public int Update(T model)
        {
            var tp = typeof(T);
            var pk = GetPrimaryKey(); //获取主键
            var props = tp.GetProperties().ToList();
            //获取所有的属性名称(除主键)
            var propNames = props.Where(p => !p.Name.Equals(pk)).Select(p => p.Name).ToList();
    
    
            //update 表 set 字段1=@字段1,字段2=@字段2, where 主键名=主键值
            string sql = $"update {tp.Name} set ";
            foreach (var propName in propNames)
            {
                sql += $"{propName}=@{propName},";
            }
    
            sql = sql.Remove(sql.Length - 1);
    
            sql += $" where {pk.Name}=@{pk.Name}";
    
            List<SqlParameter> list = new();
            foreach (var prop in props)
            {
                SqlParameter parameter = new SqlParameter(prop.Name, prop.GetValue(model));
                list.Add(parameter);
            }
    
            return DbHelper.ExecuteNonQuery(sql, list.ToArray());
        }
    
        public int Delete(dynamic id)
        {
            //delete from 表名 where 主键名=@主键值
    
            var pk = GetPrimaryKey().Name;
            return DbHelper.ExecuteNonQuery($"delete from {typeof(T).Name} where {pk}=@{pk}", new SqlParameter(pk,id));
        }
    
    
        /// <summary>
        /// 获取主键
        /// </summary>
        /// <returns></returns>
        public PropertyInfo GetPrimaryKey()
        {
            var props = typeof(T).GetProperties();
            foreach (var propertyInfo in props)
            {
                //获取特性
                var attrs = propertyInfo.GetCustomAttributes(typeof(KeyAttribute), false);
                if (attrs.Length > 0)
                {
                    return propertyInfo;
                }
            }
    
            return props[0]; // 如果没有Key 特性,就让第一个属性当作主键
        }
    }

     

     

DataTable 转 List

DataTable 转换成List

private static List<T> ToList<T>(DataTable dt) where T : class, new()
{
    Type t = typeof(T);
    PropertyInfo[] propertys = t.GetProperties();
    List<T> lst = new List<T>();
    string typeName = string.Empty;

    foreach (DataRow dr in dt.Rows)
    {
        T entity = new T();
        foreach (PropertyInfo pi in propertys)
        {
            typeName = pi.Name;
            if (dt.Columns.Contains(typeName))
            {
                if (!pi.CanWrite) continue;
                object value = dr[typeName];
                if (value == DBNull.Value) continue;
                if (pi.PropertyType == typeof(string))
                {
                    pi.SetValue(entity, value.ToString(), null);
                }
                else if (pi.PropertyType == typeof(int) || 
                         pi.PropertyType == typeof(int?))
                {
                    pi.SetValue(entity, int.Parse(value.ToString()), null);
                }
                else if (pi.PropertyType == typeof(DateTime?) || 
                         pi.PropertyType == typeof(DateTime))
                {
                    pi.SetValue(entity, DateTime.Parse(value.ToString()), null);
                }
                else if (pi.PropertyType == typeof(float))
                {
                    pi.SetValue(entity, float.Parse(value.ToString()), null);
                }
                else if (pi.PropertyType == typeof(double))
                {
                    pi.SetValue(entity, double.Parse(value.ToString()), null);
                }
                else
                {
                    pi.SetValue(entity, value, null);
                }
            }
        }

        lst.Add(entity);
    }

    return lst;
}

配套视频链接:

C#/.Net高级进阶 - 网易云课堂 (163.com)

 

 

 

到了这里,关于第四单元 泛型的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用js编写一个函数判断所有数据类型的通用方法

    一、判断数据类型的方法 1、typeof 在 JavaScript 里使用 typeof 来判断数据类型,只能区分基本类型,即 “number”,”string”,”undefined”,”boolean”,”object” 五种。 对于数组、对象来说,其关系错综复杂,使用 typeof 都会统一返回 “object” 字符串。 要想区别对象、数组单

    2024年02月22日
    浏览(42)
  • 深入理解Java泛型:编写灵活而安全的代码

    1. 泛型的概念 泛型可以看作是参数化的类型,就像函数式编程中的高阶函数,它们可以接受多种类型的参数。在Java中,泛型主要用在类、接口和方法上。 2. 泛型的作用 类型安全 :泛型可以确保在编译时就能发现类型错误,而不是在运行时。 代码复用 :通过泛型,可以编写

    2024年04月14日
    浏览(41)
  • C# 泛型类型详解:编写更安全、可重用的代码

    在C#中,泛型类型是一种强大的特性,它允许我们编写更加灵活和可重用的代码。本文将详细介绍泛型类型的概念、优势以及使用方法,并提供一些示例来帮助新手更好地理解 泛型类型是一种在编译时能够指定类型参数的类型。它允许我们在定义类、接口、方法等时,不指定

    2024年02月20日
    浏览(42)
  • 编写一个类为Rectangle。该类有两个属性: 长(length)和宽(width),计算矩形面积周长。写另外一个类,来检验自己编写的类中的方法是否正确。

    编写一个类为Rectangle. 该类有两个属性: 长(length)和宽(width)默认值为1,该两个变量为私有的,即private. 其它方法为: 构造方法:public Rectangle();   Public Rentangle(double length, double width); 方法: 矩形对象调用该方法,可以得到面积:public double getArea() 矩形对象调用该方法,可以

    2024年02月05日
    浏览(39)
  • 编写一个Student类,包含name和age属性,提供有参构造方法

    2. 请按照下列要求编写程序。 ( 1 ) 编写一个 Student 类,包含 name 和 age 属性,提供有参构造方法。 ( 2 ) 在 Student 类中,重写 toString() 方法,输出 age 和 name 的值。 ( 3 ) 在 Student 类中,重写 hashCode() 和 equals() 方法 hashCode() 的返回值是 name 的 hash 值与 age 的和。 equals(

    2024年02月11日
    浏览(32)
  • 第四单元 视图与模型

      不同的业务场景,我们最好是建立不同的ViewModel来与与之对应。我们弄个实际开发的例子来进行说明。   此时前端需要展示的数据项如下: 商品名,价格,分类,添加人,维护人,最后维护时间 此时你的商品视图模型应该是:   使用视图模型的好处: 面对那些业务场景

    2024年02月05日
    浏览(27)
  • 第四单元 表约束

    为了维护数据表的数据完整性而设定的一系列规则, 防止用户在数据表中插入一些错误的数据 . 主键约束:保证数据的完整性,唯一性,原子性(Id:编号) 外键约束 分类表数据: Id 分类名称 1 衣服 2 手机 3 电器 商品表数据: 商品编号 商品名称 价格 所属分类(外键) 1 李宁

    2024年02月05日
    浏览(27)
  • 第四单元 类型转换

    指的是从一种类型,转换为另外一种类型。例如:“123” 字符串 可以转换为 整形 123。 类型转换分为:强制转换 (显示转换)与 隐式转换 按内存分类:装箱,拆箱 对于内置数值类型,如果要存储的值无需截断或四舍五入即可适应变量,则可以进行隐式转换。 对于整型类型

    2024年02月06日
    浏览(24)
  • 第四单元 管理数据库架构

    EF Core 提供两种主要方法来保持 EF Core 模型和数据库架构同步。至于我们应该选用哪个方法,请确定你是希望以 EF Core 模型为准还是以数据库为准。 如果希望以 EF Core 模型为准,请使用 迁移 。 对 EF Core 模型进行更改时,此方法会以增量方式将相应架构更改应用到数据库,以

    2024年02月05日
    浏览(53)
  • 【单元测试】--编写单元测试

    一、编写第一个单元测试 编写第一个单元测试通常包括以下步骤。以下示例以C#和NUnit为例: 创建测试项目 : 在Visual Studio中,创建一个新的Class Library项目,这将是你的单元测试项目。 在解决方案资源管理器中,右键点击项目,选择 “管理 NuGet 包”,然后搜索并安装NUnit框

    2024年02月07日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包