目录
先看一段介绍
泛型(Generic),是将不确定的类型预先定义下来的一种C#高级语法,我们在使用一个类,接口或者方法前,不知道用户将来传什么类型,或者我们写的类,接口或方法相同的代码可以服务不同的类型,就可以定义为泛型。这会大大简化我们的代码结构,同时让后期维护变得容易。
泛型很适用于集合,我们常见的泛型集合有:List<T>,Dictionary<K,V>等等(T,K,V就代表不确定的类型,它是一种类型占位符),无一不是利用的泛型这一特性,若没有泛型,我们会多出很多重载方法,以解决类型不同,但是执行逻辑相同的情况。
看看上面这一堆介绍看着都想睡觉了,没办法,CSDN要求文字个数不够就不给推荐,估计这些简介也没几个人会去仔细看的哈,哈哈哈。
废话一大堆,那么泛型到底长啥样?,看到下面代码中的 “T” 没有,那就是泛型。
namespace 泛型
{
class Program
{
static void Main(string[] args)
{
Test<string>();
Console.ReadKey();
}
static void Test<T>()
{
Console.WriteLine(typeof(T).FullName);
}
}
}
输出:System.String
你可能会问,这个 “T” 就是泛型啊?把 “T” 改成鸡,那鸡不也是泛型了?
namespace 泛型
{
class Program
{
static void Main(string[] args)
{
Test<string>();
Console.ReadKey();
}
static void Test<鸡>()
{
Console.WriteLine("鸡你太美");
}
}
}
是的,没错,输出:鸡你太美
先看一个例子
namespace 泛型
{
class Program
{
static void Main(string[] args)
{
int result = Test.Add(3, 4);
Console.WriteLine(result);
Console.ReadKey();
}
}
public class Test
{
public static int Add(int x, int y)
{
return x + y;
}
public static int Add(string x, string y)
{
return int.Parse(x) + int.Parse(y);
}
}
}
比如,我们需要封装一个加法的算法,但是传入的类型,可能有 int 类型,也可能有 string 类型,还可能有其他的类型,但是每多一种类型,你就要多加一个方法,非常的麻烦,臃肿。
那么有没有方法来解决这个问题呢,于是后面C#2.0中,就有了泛型这个概念,它并不是语法糖,看字面意思像 “泛滥的类型” 的意思。
那么下面就用泛型的方式来封装一下这个加法运算。
注意一下写法,在 Add 后面要加上 <T> 这句,不然会报错。

正确方式

下面是封装加法的完整代码
public class Test
{
public static int Add<T>(T x,T y)
{
try
{
return int.Parse(x.ToString()) + int.Parse(y.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return -1;
}
}
测试第一种
class Program
{
static void Main(string[] args)
{
int result = Test.Add("3", "4");
Console.WriteLine(result);
Console.ReadKey();
}
}
输出:7
测试第二种
class Program
{
static void Main(string[] args)
{
int result = Test.Add(4, 5);
Console.WriteLine(result);
Console.ReadKey();
}
}
输出:9
看了上面的案例,你会觉得,泛型是不是 Object 类型不是也差不多?下面是一些介绍:
C# 中 Object 是一切类型的基类,可以用来表示所有类型,而泛型是指将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。
你可以将泛型理解成替换,在使用的时候将泛型参数替换成具体的类型,这个过程是在编译的时候进行的,使用泛型编译器依然能够检测出类型错误。泛型不用拆箱装箱。
而 Object 表示其他类型是通过类型转换来完成的,而所有类型转化为 Object 类型都是合法的,所以即使你先将 Object 对象赋值为一个整数再赋值为一个字符串,编译器都认为是合法的。
Object 类型
> 优点:
> 1. object类型可以用来引用任何类型的实例;
> 2. object类型可以存储任何类型的值;
> 3. 可以定义object类型的参数;
> 4. 可以把object作为返回类型。
> 缺点:
> 1. 会因为程序员没有记住使用的类型而出错,造成类型不兼容;
> 2. 值类型和引用类型的互化即装箱拆箱使系统性能下降。
泛型,Object 类型,var 类型,这三种类型,看着很类似,但其实泛型的作用更不止如此,还有泛型类,泛型方法,泛型接口,泛型约束等,下面分别来介绍这几个功能
泛型类是指这个类的某些字段的类型是不确定的,只有在构造的时候才能确定下的类,泛型类一般在数据结构中用的比较多,比如 C# 自带的 List<T>,Dictionary<TKey, TValue> 等,我也写过自定义 List 相关的帖子,有兴趣的可以去看下
C# 自定义List_熊思宇的博客-CSDN博客_c#定义list
1.泛型类案例
下面看一个例子
namespace 泛型
{
class Program
{
static void Main(string[] args)
{
Test<int> test = new Test<int>();
test.Add(1);
test.Add(3);
Console.WriteLine(test.Count);
Console.ReadKey();
}
}
public class Test<T>
{
private List<T> list = new List<T>();
public int Count => list.Count;
public void Add(T t)
{
if(t != null)
list.Add(t);
}
}
}
运行后输出:2
Test 类此时并没有添加任何的约束,这个是不推荐的,至少应该加一个,如下
public class Test<T> where T : new()
{
private List<T> list = new List<T>();
public int Count => list.Count;
public void Add(T t)
{
if(t != null)
list.Add(t);
}
}
关于泛型约束,在后面的章节中会有详细的介绍。
2.泛型类继承
泛型类型继承写法:
public class Test1<T>
{
}
public class Test2<T> : Test1<T>
{
}
指定基类的类型写法:
public class Test1<T>
{
}
public class Test2<T> : Test1<int>
{
}
关于泛型类的继承,后面我再单独写文章进行介绍吧
泛型方法是指通过泛型来约束方法中的参数类型,如果没有泛型,每次方法中的参数类型都是固定的,不能随意更改,在使用泛型后,方法中的数据类型则有指定的泛型来约束,即可以根据提供的泛型来传递不同类型的参数。
泛型方法的返回类型和参数类型都可以使用泛型。如下:
public static T GetT<T>(T a)
{
return a;
}
泛型方法同样可以使用泛型约束,只是泛型方法中的 T 只能用在方法内部和返回值,而泛型类中的 T 可以应用于整个类的字段和方法。
public class Test
{
public void Add<T>(T t) where T : new()
{
List<T> list = new List<T>();
if (t != null) list.Add(t);
}
}
如果要对 T 类型作一些操作,则需要用到反射。
泛型接口如下
public interface IFace<T>
{
void SayHi(T msg);
}
同样的,泛型接口也可以使用泛型约束
public interface IFace<T> where T : new()
{
void SayHi(T msg);
}
实现泛型接口有两种情况
/// <summary>
/// 1.普通类实现泛型接口
/// </summary>
public class MyClass1 : IFace<string>
{
public void SayHi(string msg)
{
Console.WriteLine(msg);
}
}
/// <summary>
/// 2.泛型类继承泛型接口
/// </summary>
/// <typeparam name="T"></typeparam>
public class MyClass2<T> : IFace<T>
{
public void SayHi(T msg)
{
Console.WriteLine(msg);
}
}
案例
namespace 泛型
{
class Program
{
static void Main(string[] args)
{
IFace<Msg> face = new Test();
face.SayHi(new Msg());
Console.ReadKey();
}
}
public interface IFace<T> where T : new()
{
void SayHi(T msg);
}
public class Test : IFace<Msg>
{
public void SayHi(Msg msg)
{
Console.WriteLine(msg.MyName);
}
}
public class Msg
{
public string MyName = "张三";
}
}
输出:张三
泛型委托和普通委托差别不大,只是参数由具体类型变成了 "T"
public delegate void MyDelegate<T>(T args);
案例
namespace 泛型
{
class Program
{
delegate void MyDelegate<T>(T args);
static void Main(string[] args)
{
MyDelegate<string> myDelegate = SayHi;
myDelegate("哈喽");
Console.ReadKey();
}
static void SayHi(string msg)
{
Console.WriteLine(msg);
}
}
}
输出:哈喽
泛型约束就是对 “T” 类型的数据做一定的限制,防止在运用过程中,做一些违规的操作,来保证数据的安全。
六种类型的约束:
| T:结构 | 类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。有关更多信息,请参见使用可空类型(C# 编程指南)。 |
| T:类 | 类型参数必须是引用类型,包括任何类、接口、委托或数组类型。 |
| T:new() | 类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。 |
| T:<基类名> | 类型参数必须是指定的基类或派生自指定的基类。 |
| T:<接口名称> | 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。 |
| T:U | 为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。 |
案例
namespace 泛型
{
class Program
{
static void Main(string[] args)
{
Console.ReadKey();
}
}
/// <summary>
/// 泛型接口
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IFace<T>
{
void SayHi(T msg);
}
/// <summary>
/// 泛型约束
/// </summary>
public class MyClass1<T, K, V, W, X,Y>
where T : struct //约束 T 必须是值类型
where K : class //约束 K 必须是引用类型
where V : IFace<T> //约束 V 必须实现 IFace 接口
where W : K //约束 W 必须是 K 类型,或者是 K 类型的子类
where X : class, new() //约束 X 必须是引用类型,并且有一个无参数的构造函数,当有多个约束时,new()必须写在最后
where Y : MyClass2 //约束 Y 必须是 MyClass2 类型,或者继承于 MyClass2 类
{
public void Add(T num)
{
Console.WriteLine(num);
}
}
public class MyClass2
{
}
}
这个案例,几乎介绍了所有的泛型约束的使用方法,有兴趣的可以亲自动手写一写。
现在有一个需求,用户修改个人资料,在提交这里必须要判断是否有修改内容,如果没有修改,点击提交按钮则不提交,那要怎么判断呢?
有人可能会说,用 if...else 去判断就好了,可以这么写没错,但资料如果有几百个地方改了,不会还用 if...else 吧,那整篇代码全是 if...else 了,就像我同事开玩笑一样:“我不会高数,我只会写 if...else...”
下面这个案例就教你如何来解决这个问题。
代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace 泛型
{
class Program
{
static void Main(string[] args)
{
Test test1 = new Test();
test1.Names = "张三";
test1.Age = 24;
test1.Height = 179.9f;
Test test2 = new Test();
test2.Names = "李四";
test2.Age = 31;
test2.Height = 163.4f;
bool result = CompareType(test1, test2);
Console.WriteLine("是否相同:" + result);
Console.ReadKey();
}
/// <summary>
/// 比较--两个类型一样的实体类对象的值
/// </summary>
/// <param name="oneT"></param>
/// <returns>返回true表示两个对象的数据相同,返回false表示不相同</returns>
public static bool CompareType<T>(T oneT, T twoT)
{
bool result = true;//两个类型作比较时使用,如果有不一样的就false
Type typeOne = oneT.GetType();
Type typeTwo = twoT.GetType();
//如果两个T类型不一样 就不作比较
if (!typeOne.Equals(typeTwo)) { return false; }
PropertyInfo[] pisOne = typeOne.GetProperties(); //获取所有公共属性(Public)
PropertyInfo[] pisTwo = typeTwo.GetProperties();
//如果长度为0返回false
if (pisOne.Length <= 0 || pisTwo.Length <= 0)
{
return false;
}
//如果长度不一样,返回false
if (!(pisOne.Length.Equals(pisTwo.Length))) { return false; }
//遍历两个T类型,遍历属性,并作比较
for (int i = 0; i < pisOne.Length; i++)
{
//获取属性名
string oneName = pisOne[i].Name;
string twoName = pisTwo[i].Name;
//获取属性的值
object oneValue = pisOne[i].GetValue(oneT, null);
object twoValue = pisTwo[i].GetValue(twoT, null);
//比较,只比较值类型
if ((pisOne[i].PropertyType.IsValueType || pisOne[i].PropertyType.Name.StartsWith("String")) && (pisTwo[i].PropertyType.IsValueType || pisTwo[i].PropertyType.Name.StartsWith("String")))
{
if (oneName.Equals(twoName))
{
if (oneValue == null)
{
if (twoValue != null)
{
result = false;
break; //如果有不一样的就退出循环
}
}
else if (oneValue != null)
{
if (twoValue != null)
{
if (!oneValue.Equals(twoValue))
{
result = false;
break; //如果有不一样的就退出循环
}
}
else if (twoValue == null)
{
result = false;
break; //如果有不一样的就退出循环
}
}
}
else
{
result = false;
break;
}
}
else
{
//如果对象中的属性是实体类对象,递归遍历比较
bool b = CompareType(oneValue, twoValue);
if (!b) { result = b; break; }
}
}
return result;
}
}
public class Test
{
public string Names { get; set; }
public int Age { get; set; }
public float Height { get; set; }
}
}
输出:是否相同:False
将这两个 Test 对象的值改为一样试试
static void Main(string[] args)
{
Test test1 = new Test();
test1.Names = "张三";
test1.Age = 24;
test1.Height = 179.9f;
Test test2 = new Test();
test2.Names = "张三";
test2.Age = 24;
test2.Height = 179.9f;
bool result = CompareType(test1, test2);
Console.WriteLine("是否相同:" + result);
Console.ReadKey();
}
输出:是否相同:True
可以看到,输出结果是对的
如果这个帖子对你有用,欢迎 关注 + 点赞 + 留言,谢谢
end
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
我正在尝试设置一个puppet节点,但rubygems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由rubygems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby
我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案
我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby1.9+ 关于ruby-主要:Objectwhenrun
我有一个具有一些属性的模型:attr1、attr2和attr3。我需要在不执行回调和验证的情况下更新此属性。我找到了update_column方法,但我想同时更新三个属性。我需要这样的东西:update_columns({attr1:val1,attr2:val2,attr3:val3})代替update_column(attr1,val1)update_column(attr2,val2)update_column(attr3,val3) 最佳答案 您可以使用update_columns(attr1:val1,attr2:val2
我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)