jquery提供的serialize方法能够实现。
$("#searchForm").serialize();但是,观察输出的信息,发现serialize()方法做的是将表单中的数据以htpp请求格式拼接成字符串。serialize确实是能够解决一般的提交数据。但是有时我们需要的是一个object对象,而不是字符串(比如jqgrid reload时设置查询条件参数,就需要object对象)。方法如下:
(function(window, $) { $.fn.serializeJson = function() { var serializeObj = {}; var array = this.serializeArray(); var str = this.serialize(); $(array).each( function() { if (serializeObj[this.name]) { if ($.isArray(serializeObj[this.name])) { serializeObj[this.name].push(this.value); } else { serializeObj[this.name] = [ serializeObj[this.name], this.value ]; } } else { serializeObj[this.name] = this.value; } }); return serializeObj; }; })(window, jQuery); 调用:
console.info($("#searchForm").serializeJson());
ADO.NET进行参数化时会自动将参数值包含在单引号中,除了特殊需求,最好不要自己手动添加单引号。ADO.NET中识别参数标识是使用符号@,如果在SQL语句中将参数标识放在单引号中,单引号中的参数标识只会被当成字符串!
所以要对LIKE语句进行参数化查询时,就要先对参数值进行格式化,在传参之前就设置好通配符,具体实现代码如下:
string strSqlCommandText = "SELECT Title FROM Article WHERE Title LIKE @Title"; SqlParameter parameter = new SqlParameter() { ParameterName = "@Title", Value = "%ASP.NET MVC%", SqlDbType = SqlDbType.NVarChar, Size = 50 };
从上面的代码中我们可以看到具体的变换有两点,第一点是SQL语句中取消了通配符%并且参数标识没有被单引号包含其中,第二点则是通配符直接放到了参数值中,这样ADO.NET在进行参数化后所生成的SQL就完全没问题了,最终也能正确的查询出结果。
可以在处理Post方法的Action添加一个特性:[ValidateInput(false)],这样处理就更加有针对性,提高页面的安全性。
[HttpPost]
[ValidateInput(false)]
public ActionResult CatalogEdit(Catalog model)
{
return View();
}
$(window.parent.document).contents().find("#iframename")[0].contentWindow.iframefunction();
var province = '' ;
var city = '' ;
jQuery.getScript("http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=js",function(){
province = remote_ip_info["province"];
city = remote_ip_info["city"];
alert(city) //弹出城市名称
}) ;
【我们一起写框架】MVVM的WPF框架(三)—数据控件
这世上,没人能一次性写出完美无缺的框架;因为,任何一个框架都需要项目的淬炼,然后才能升华,趋近完美。
所以,框架是个反复修改的东西,最终形成的东西。
如果你学了一点技术,觉得自己可以写出框架了,觉得自己有架构师的能力,然而自己总是怀才不遇——那一定是你的错觉。
因为,你框架没有经过项目淬炼;而淬炼过框架的人都了解,设计的再好的框架,最终会被业务需求打的细碎,然后被开发人员搅和再一起。
所以细节决定成败,没有细节的框架就是扯淡。
DataControl—数据控件
上文我们已经编写出来了WPF的MVVM基础框架,但为了让他更加强壮,为了让他多坚持一阵子再粉碎,我们要让ViewModel更强壮,所以我们要编写[数据控件]。
数据控件其实很好理解,它就是把UI控件中存储的数据提取出来,好让ViewModel可以通过修改数据来控制UI变化;当然,为了更好的控制UI变化,数据控件里还得包含一点管理UI的属性。
因为WPF里的控件大多继承自Control,所以我们先创建Control的数据控件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public class Control<T> : INotifyPropertyChanged
{ public event PropertyChangedEventHandler PropertyChanged;
public T _DataContent ;
public T DataContent { get { return _DataContent; } set { _DataContent = value; OnPropertyChanged(); } }
public Visibility _Visibility;
public Visibility Visibility { get { return _Visibility; } set { _Visibility = value; OnPropertyChanged(); } }
public bool _IsReadOnly;
public bool IsReadOnly { get { return _IsReadOnly; } set { _IsReadOnly = value; OnPropertyChanged(); } }
public bool _IsEnabled;
public bool IsEnabled { get { return _IsEnabled; } set { _IsEnabled = value; OnPropertyChanged(); } }
protected void OnPropertyChanged([CallerMemberName] string propertyName = "" )
{
if (PropertyChanged != null )
{
PropertyChanged( this , new PropertyChangedEventArgs(propertyName));
}
}
} |
如上代码所示,我们创建了Control的数据控件。
可以看到,处理存贮数据的DataContent属性之外,还创建了一些管理UI的属性IsEnabled、IsReadOnly、Visibility。
父类数据控件创建完成后,我们开始创建子类的数据控件。[如果子类要管理的UI属性不在父类内,我们就需要额外创建一些]
TextBlock和TextBox
我们先创建最基础的,最常用的TextBlock和TextBox。
TextBlock代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class TextBlock<T> : Control<T>
{ public T _Text;
public T Text
{
get { return _Text; }
set
{
_Text = value;
OnPropertyChanged();
}
}
} |
TextBox代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class TextBox<T> : Control<T>
{ public Action<T> TextChangeCallBack = null ;
public T _Text;
public T Text {
get { return _Text; }
set
{
_Text = value;
if (TextChangeCallBack != null )
{
TextChangeCallBack(_Text);
}
OnPropertyChanged();
}
}
} |
可以看到TextBlock和TextBox都继承了Control,而他们的区别只是TextBox多了一个TextChangeCallBack。
有人会想到,那完全可以用TextBox替代TextBlock。
理论上,TextBlock是可以被替换,但为了程序清晰,还是区别开来更好。
控件定义好了,我们现在看一下如何应用。
TextBox应用
1
2
3
4
5
6
7
8
|
xaml页面代码 <TextBox Text= "{Binding ChangeTextBox.Text,Mode=TwoWay}" Margin= "5" FontSize= "12" ></TextBox>
----------------------------------<br>ViewModel页面代码 public TextBox< string > ChangeTextBox { get ; set ; }
public VM_PageTextBox()
{<br> ChangeTextBox = new TextBox< string >();
ChangeTextBox.TextChangeCallBack = (text) => { MessageBox(text); }; //声明TextChange
} |
如代码所示,我们在ViewModel中定义了ChangeTextBox属性,然后再Xaml中绑定了ChangeTextBox属性的Text到UI控件TextBox的Text属性上,这样我们就实现了数据联动。
并且代码中实例化了TextChangeCallBack委托,那么当Text数据变化时,该委托就会触发。
注意:TextChangeCallBack委托与TextChanged事件不同,并不是每次修改文字都会触发,而是当TextBox的Text内容真正被修改时,才会触发;我们可以简单的理解为TextBox失去焦点时才会触发。
这里只介绍TextBox应用,TextBlock应用就不介绍了,因为使用方式和TextBox一样。
如果想了解更多数据控件的应用,请去GitHub下载源码。
ComboBox
ComboBox稍微复杂一点,因为他多了一个ItemSource属性。
我们先看ComboBox的数据控件代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
public class ComboBox<T> : Control<T>
{ public Action<T> SelectCallBack = null ;
public ComboBox()
{
}
public ObservableCollection<T> _ItemsSource;
public ObservableCollection<T> ItemsSource
{
get
{
return _ItemsSource;
}
set
{
_ItemsSource = value;
if (_ItemsSource != null && _ItemsSource.Count > 0 && SelectedItem == null )
{
SelectedItem = _ItemsSource.First();
}
OnPropertyChanged();
}
}
public T _SelectedItem;
public T SelectedItem
{
get { return _SelectedItem; }
set
{
_SelectedItem = value;
if (SelectCallBack != null )
{
SelectCallBack(_SelectedItem);
}
OnPropertyChanged();
}
}
private ICollectionView _ItemsSourceView;
public ICollectionView ItemsSourceView
{
get
{
_ItemsSourceView = CollectionViewSource.GetDefaultView(_ItemsSource);
return _ItemsSourceView;
}
set
{
_ItemsSourceView = value;
OnPropertyChanged();
}
}
public void SetItemsSource(List<T> itemSource)
{
ItemsSource = new ObservableCollection<T>(itemSource);
}
} |
代码相对简单,SelectedItem和ItemsSource用来绑定UI控件ComboBox的同名属性。
ItemsSourceView:ItemsSourceView属性可能有些难理解,这里我们简单介绍一下。
因为WPF的UI控件被创建以后,要被添加到视觉树中,所以最终会被显示在屏幕上的是包裹着控件的视觉树;其中视觉树与控件是可以分离的;比如控件中绑定的数据是10行,而视觉树可以显示3行。
为了管理视觉树,我们创建了ItemsSourceView属性。
因为ItemsSourceView是ICollectionView类型,所以ItemsSourceView可以处理排序、筛选和分组。[有兴趣的同学可以自行了解下ICollectionView类型]
感觉这样描述还是很难理解,让我们一起在应用中慢慢理解吧。
ObservableCollection:我们可以看到ItemsSource是类型是ObservableCollection,而不是List。为什么要用ObservableCollection呢?
很简单,因为ObservableCollection继承了INotifyCollectionChanged,即,数据控件进行[行]的增删,也会让UI进行[行]的增删。
ComboBox应用
在应用之前,我们先在Proxy建立一个获取数据是代理。
创建获取数据的方法如下:
1
2
3
4
5
6
|
public List<User> GetComboBoxData()
{ List<User> userList = new List<User>();
User user1 = new User() { Id = 1, Name = "张三" , Age = 11 };
userList.Add(user1);
return userList;<br>}
|
Xaml页面代码如下:
1
|
<ComboBox Margin= "5" Width= "200" FontSize= "12" ItemsSource= "{Binding TestComboBox.ItemsSource}" DisplayMemberPath= "Name" SelectedValuePath= "Id" SelectedItem= "{Binding TestComboBox.SelectedItem}" ></ComboBox>
|
ViewModel代码如下:
1
2
3
4
5
6
7
8
9
10
|
public ComboBox<User> TestComboBox { get ; set ; }
TestDataProxy proxy = new TestDataProxy();
public VM_PageComboBox()
{ TestComboBox = new ComboBox<User>();
TestComboBox.SetItemsSource(proxy.GetComboBoxData());
TestComboBox.SelectCallBack = (user) => {
MessageBox(user.Name);
};
} |
如上所示,我们已经实行了在ViewModel中管理ComboBox。
----------------------------------------------------------------------------------------------------
本篇文章就先讲到这了,下一篇文章我们将一起为框架编写DataGrid数据控件。
因为DataGrid数据控件是所有数据控件中最复杂的,而且代码量特别多;所以,我决定,单拿出一篇来介绍DataGrid。
框架代码已经传到Github上了,并且会持续更新。
相关文章:
To be continued——DataGrid
Github地址:https://github.com/kiba518/KibaFramework
----------------------------------------------------------------------------------------------------
严格意义上来说,简单工厂模式并不属于GoF的23种设计模式,但是它是学习其他工厂模式的基础和前提条件。理解了简单工厂模式,学习工厂方法模式和抽象工厂模式会比较容易一些。
简单工厂模式的定义
定义一个工厂类,他可以根据不同的参数返回不同类的实例。通常情况下,被创建的类的实例通常都具有共同的父类。
简单工厂模式又可以称之为静态工厂方法模式,因为创建对象实例的方法通常都是静态方法。
在简单工厂模式中,只需要记住一点。一个简单的参数可以即可获得所需的对象实例。
简单工厂模式的UML图如下:
简单工厂模式包含三个角色:
1、工厂角色(Factory):该类是简单工厂的核心,我们所需要的对象实例就是这个类帮我们创建的,它里面有一个静态方法GetProduct(string productType),我们通过传入产品类型(productType)来得到我们想要的产品。
2、抽象产品角色(AbstractProduct):该类是所有产品的父类。它可以是接口或者抽象类。只有这样我们才能给工厂的GetProduct(string productType)方法定义返回值。
3、实体产品(ConcreteProductA,ConcreteProductB,ConcreteProductC):这些类是简单工厂最终要创建的目标,也是客户端最终想要的对象实例。
现在来假设一种场景,我们开发过程中,肯定会用到按钮,圆形按钮、正方形按钮、矩形按钮等等,在程序的很多地方我们都可能会复用这些按钮。我们可以通过一个简单工厂来帮助我们创建这些按钮。
首先我们要声明工厂要生产的产品(各种按钮)类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public class Button
{ public virtual void OnClick()
{
}
} public class RoungButton:Button
{ public override void OnClick()
{
Console.WriteLine( "点击了圆形按钮!" );
}
} public class SquareButton:Button
{ public override void OnClick()
{
Console.WriteLine( "点击了正方形按钮!" );
}
} public class RectangleButton:Button
{ public override void OnClick()
{
Console.WriteLine( "点击了矩形按钮!" );
}
} |
其次是我们的(静态工厂类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class ButtonFactory
{
public static Button CreateButton( string type)
{
Button btnObj = null ;
switch (type)
{
case "round" :
btnObj = new RoungButton();
break ;
case "square" :
btnObj = new SquareButton();
break ;
case "rect" :
btnObj = new RectangleButton();
break ;
}
return btnObj;
}
}
|
在此处还有一些要说明的要点。
简单工厂类帮助我们创建的是一类产品,这一类产品有一个共同的父类,这个父类是作为工厂方法的返回值来使用的。如果没有这个共同的父类,我们的程序就需要用object作为返回值,然后在程序中进行强制转换,这个明显是非常不友好的编程。而作为一名程序员,在程序中进行大量的装箱和拆箱操作是比较耗费性能的,良好的编程习惯中,也应该尽量减少装箱拆箱的操作。
然后我们就可以在控制台调试我们的程序了
1
2
3
4
5
6
7
|
static void Main( string [] args) //文件名的修改
{ Button roundBtn = ButtonFactory.CreateButton( "round" );
roundBtn.OnClick();
Button squareBtn = ButtonFactory.CreateButton( "square" );
squareBtn.OnClick();
} |
程序的运行结果如下图:
最后给大家介绍一个和技术相关的付费问答平台、阅读平台-技易 欢迎访问:http://www.seekwd.com
追书神器API
由于自己喜欢看小说,有的时候不方便手机看的时候希望在电脑上面看,但很多网站有广告啊,于是封装了套手机版的追书神器API
目前只做了搜索 详情 书评 换源 正文
调用方式:
//搜索小说 var searchResult = BookApi.Search("凡人修仙传"); //筛选 var book = searchResult.books.FirstOrDefault(x=>x.author == "忘语"); //获取小说描述 var detail = BookApi.GetBookDetails(book._id); System.Console.WriteLine(detail.JsonSerialize()); System.Console.WriteLine(); //获取小说书评 var comment = BookApi.GetComment(book._id, 0, 30); System.Console.WriteLine(comment.total); //获取小说源 var sources = BookApi.GetBookSource(book._id); foreach (var bookSource in sources) { System.Console.WriteLine(bookSource.name); } //获取小说目录 var catalog = BookApi.GetBookCatalog(sources[0]._id); foreach (var catalogChapter in catalog.chapters) { System.Console.WriteLine(catalogChapter.title); } //获取指定章节正文 var bookContent = BookApi.GetBookContent(catalog.chapters[0].link); System.Console.WriteLine(bookContent.chapter.cpContent);
链接: https://pan.baidu.com/s/1pMNgMqZ 密码: kpy6
【c#】队列(Queue)和MSMQ(消息队列)的基础使用
首先我们知道队列是先进先出的机制,所以在处理并发是个不错的选择。然后就写两个队列的简单应用。
Queue
命名空间
命名空间:System.Collections,不在这里做过多的理论解释,这个东西非常的好理解。
可以看下官方文档:https://docs.microsoft.com/zh-cn/dotnet/api/system.collections.queue?view=netframework-4.7.2
示例代码
我这里就是为了方便记忆做了一个基本的例子,首先创建了QueueTest类:
包含了获取队列的数量,入队和出队的实现
1 public class QueueTest 2 { 3 public static Queue<string> q = new Queue<string>(); 4 5 #region 获取队列数量 6 public int GetCount() 7 { 8 9 return q.Count; 10 } 11 #endregion 12 13 #region 队列添加数据 14 public void IntoData(string qStr) 15 { 16 string threadId = System.Threading.Thread.CurrentThread.ManagedThreadId.ToString(); 17 q.Enqueue(qStr); 18 Console.WriteLine($"队列添加数据: {qStr};当前线程id:{threadId}"); 19 } 20 #endregion 21 22 #region 队列输出数据 23 24 public string OutData() 25 { 26 string threadId = System.Threading.Thread.CurrentThread.ManagedThreadId.ToString(); 27 string str = q.Dequeue(); 28 Console.WriteLine($"队列输出数据: {str};当前线程id:{threadId}"); 29 return str; 30 } 31 #endregion 32 33 }
为了模拟并发情况下也不会出现重复读取和插入混乱的问题所以写了TaskTest类里面开辟了两个异步线程进行插入和读取:
1 class TaskTest 2 { 3 4 #region 队列的操作模拟 5 public static void QueueMian() 6 { 7 QueueA(); 8 QueueB(); 9 } 10 private static async void QueueA() 11 { 12 QueueTest queue = new QueueTest(); 13 var task = Task.Run(() => 14 { 15 for (int i = 0; i < 20; i++) 16 { 17 queue.IntoData("QueueA" + i); 18 } 19 }); 20 await task; 21 Console.WriteLine("QueueAA插入完成,进行输出:"); 22 23 while (queue.GetCount() > 0) 24 { 25 queue.OutData(); 26 } 27 } 28 29 private static async void QueueB() 30 { 31 QueueTest queue = new QueueTest(); 32 var task = Task.Run(() => 33 { 34 for (int i = 0; i < 20; i++) 35 { 36 queue.IntoData("QueueB" + i); 37 } 38 }); 39 await task; 40 Console.WriteLine("QueueB插入完成,进行输出:"); 41 42 while (queue.GetCount() > 0) 43 { 44 queue.OutData(); 45 } 46 } 47 #endregion 48 49 }
效果展示
然后在main函数直接调用即可:
通过上面的截图可以看出插入线程是无先后的。
这张图也是线程无先后。
MSMQ
msmq是微软提供的消息队列,本来在windows系统中就存在,但是默认没有开启。需要开启。
开启安装
打开控制面板=>程序和功能=> 启动或关闭windows功能 => Microsoft Message Queue(MSMQ)服务器=>Microsoft Message Queue(MSMQ)服务器核心
一般选择:MSMQ Active Directory域服务继承和MSMQ HTTP支持即可。
点击确定等待安装成功。
命名空间
需要引用System.Messaging.DLL
命名空间:System.Messaging
官方资料文档:https://docs.microsoft.com/zh-cn/dotnet/api/system.messaging.messagequeue?view=netframework-4.7.2
示例代码
与上面queue同样的示例方式,创建一个MSMQ类,实现创建消息队列,查询数据,入列,出列功能:
1 /// <summary> 2 /// MSMQ消息队列 3 /// </summary> 4 class MSMQ 5 { 6 static string path = ".\\Private$\\myQueue"; 7 static MessageQueue queue; 8 public static void Createqueue(string queuePath) 9 { 10 try 11 { 12 if (MessageQueue.Exists(queuePath)) 13 { 14 Console.WriteLine("消息队列已经存在"); 15 //获取这个消息队列 16 queue = new MessageQueue(queuePath); 17 } 18 else 19 { 20 //不存在,就创建一个新的,并获取这个消息队列对象 21 queue = MessageQueue.Create(queuePath); 22 path = queuePath; 23 } 24 } 25 catch (Exception e) 26 { 27 Console.WriteLine(e.Message); 28 } 29 30 } 31 32 33 #region 获取消息队列的数量 34 public static int GetMessageCount() 35 { 36 try 37 { 38 if (queue != null) 39 { 40 int count = queue.GetAllMessages().Length; 41 Console.WriteLine($"消息队列数量:{count}"); 42 return count; 43 } 44 else 45 { 46 return 0; 47 } 48 } 49 catch (MessageQueueException e) 50 { 51 52 Console.WriteLine(e.Message); 53 return 0; 54 } 55 56 57 } 58 #endregion 59 60 #region 发送消息到队列 61 public static void SendMessage(string qStr) 62 { 63 try 64 { 65 //连接到本地队列 66 67 MessageQueue myQueue = new MessageQueue(path); 68 69 //MessageQueue myQueue = new MessageQueue("FormatName:Direct=TCP:192.168.12.79//Private$//myQueue1"); 70 71 //MessageQueue rmQ = new MessageQueue("FormatName:Direct=TCP:121.0.0.1//private$//queue");--远程格式 72 73 Message myMessage = new Message(); 74 75 myMessage.Body = qStr; 76 77 myMessage.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) }); 78 79 //发生消息到队列中 80 81 myQueue.Send(myMessage); 82 83 string threadId = System.Threading.Thread.CurrentThread.ManagedThreadId.ToString(); 84 Console.WriteLine($"消息发送成功: {qStr};当前线程id:{threadId}"); 85 } 86 catch (MessageQueueException e) 87 { 88 Console.WriteLine(e.Message); 89 } 90 } 91 #endregion 92 93 #region 连接消息队列读取消息 94 public static void ReceiveMessage() 95 { 96 MessageQueue myQueue = new MessageQueue(path); 97 98 99 myQueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) }); 100 101 try 102 103 { 104 105 //从队列中接收消息 106 107 Message myMessage = myQueue.Receive(new TimeSpan(10));// myQueue.Peek();--接收后不消息从队列中移除 108 myQueue.Close(); 109 110 string context = myMessage.Body.ToString(); 111 string threadId = System.Threading.Thread.CurrentThread.ManagedThreadId.ToString(); 112 Console.WriteLine($"--------------------------消息内容: {context};当前线程id:{threadId}"); 113 114 } 115 116 catch (System.Messaging.MessageQueueException e) 117 118 { 119 120 Console.WriteLine(e.Message); 121 122 } 123 124 catch (InvalidCastException e) 125 126 { 127 128 Console.WriteLine(e.Message); 129 130 } 131 132 } 133 #endregion 134 }
这里说明一下path这个字段,这是消息队列的文件位置和队列名称,我这里写的“.”(点)就是代表的位置MachineName字段,,代表本机的意思
然后TaskTest类修改成这个样子:
1 class TaskTest 2 { 3 4 #region 消息队列的操作模拟 5 public static void MSMQMian() 6 { 7 MSMQ.Createqueue(".\\Private$\\myQueue"); 8 MSMQA(); 9 MSMQB(); 10 Console.WriteLine("MSMQ结束"); 11 } 12 private static async void MSMQA() 13 { 14 var task = Task.Run(() => 15 { 16 for (int i = 0; i < 20; i++) 17 { 18 MSMQ.SendMessage("MSMQA" + i); 19 } 20 }); 21 await task; 22 Console.WriteLine("MSMQA发送完成,进行读取:"); 23 24 while (MSMQ.GetMessageCount() > 0) 25 { 26 MSMQ.ReceiveMessage(); 27 } 28 } 29 30 private static async void MSMQB() 31 { 32 var task = Task.Run(() => 33 { 34 for (int i = 0; i < 20; i++) 35 { 36 MSMQ.SendMessage("MSMQB" + i); 37 } 38 }); 39 await task; 40 Console.WriteLine("MSMQB发送完成,进行读取:"); 41 42 while (MSMQ.GetMessageCount() > 0) 43 { 44 MSMQ.ReceiveMessage(); 45 } 46 } 47 #endregion
效果展示
本机查看消息队列
创建成功的消息队列我们可以在电脑上查看:我的电脑=>管理 =>计算机管理 =>服务与应用程序 =>消息队列 =>专用队列就看到我刚才创建的消息队列
C# GetHashCode、Equals函数和键值对集合的关系
C# GetHashCode、Equals函数和键值对集合的关系
说明
HashCode:Hash码。
特性:两个值,相同的的值生成的Hash肯定相同,Hash不同的值肯定不同。
下面一张图中,只有和“错号”一行或一列的才有意义。
作用:求Hash值效率比引用类型判断是否相等的函数Equals更快,所以被用来辅助判断键值对集合的键值是否已经存在。
说明:Equals和GetHashCode都是object的虚方法,可以被任意类型进行重写。Equals来判断两个值是否相等时用到了反射所以慢一点。
在键值对集合如HashTable中,添加键值对时,会分两步检验键值是否已经存在:第一步检查所添加的键的Hash值是否存在,不存在则直接添加;如果已经存在的话开始第二步,开始检查键是否存在Equals。
namespace JustTest { class Program { static void Main(string[] args) { var t = new Hashtable(); t.Add(new user { }, 1); t.Add(new user { }, 2); } } class user { public override int GetHashCode() { Console.WriteLine("GetHashCode校验"); return 1; } public override bool Equals(object o) { Console.WriteLine("Equals校验"); return base.Equals(o); } } }
启示
可以进行代码优化:判断对象(或者字符串)是否存在时常常看到直接Equals的,但是更好的做法就是先判断两个值的hashcode是否相等,不相等的话直接添加就好了,如果相等的话然后再Equals判断是否相等
场合:注册时判断用户名是否存在等。
《CLR via C#》读书笔记(一)——CLR的执行模式
前言
万事开头难,很早之前就想写博客记录些东西,迟迟未行动,甚是遗憾。原因诸多,大体上无非都是懒、没意志力等等。这次从自己的读书笔记开始,兴许能够有所改变。
一、CLR概念
CLR(Common Language Runtime,译为公共语言运行时)是一个可由多种编程语言使用的“运行时”。CLR的核心功能:内存管理、程序集加载、安全性、异常处理和线程同步,可由面向CLR的所有语言(C++、C#、VB、F#等等)使用。无论哪种语言,最后通过编译器的结果都是托管模块。托管模块是PE32文件或PE32+文件,都需要CLR才能执行。
组成部分 | 说明 |
PE32或PE32+头 | 标准windows PE文件头,如果这个头是PE32格式,文件能在Windows32位和64位上运行,如果是PE64,文件只能在Windows的64位上运行。这个头还表示了文件类型,,并包含一个时间标记来指出文件的生成时间。对于只包含IL代码的模块,这些信息大部分都会被忽略。如果是包含本机CPU代码的模块,这个头包含本机CPU代码有关的信息。 |
CLR头 | 包含使这个模块成为托管模块的信息。头中包含要求的CLR版本,一些标志(flag),拖过模块入口方法的元数据token以及模块的元数据、资源,强模块、一些标志以及不太重要的数据项位置/大小 |
元数据 |
每个托管模块都包含元数据表。分为两类: 1、描述源代码中定义的类型和成员。 2、描述源代码引用的类型和成员。 元数据有诸多用途,例如: 1、元数据表面了编译时对原生C/C++和库文件的需求,因为在实现类型/成员的IL代码文件中,已包含有关引用类型/成员的全部信息。编译器直接从托管模块中读取元数据。 2、IDE的“智能感知”能够解析元数据,从而得到一个类型所提供的方法、属性、事件和字段。 3、CLR的代码验证过程使用元数据确保代码只执行“类型安全”的操作 4、元数据允许将对象的字段序列化到内存,将其发送给另一台机器,然后反序列化,在远程机器上重建对象状态 5、元数据允许垃圾回收器跟踪对象生存期,垃圾回收器能判断任何对象的类型并从元数据知道那个对象中的哪些字段引用了其他对象 |
IL代码 | IL代码也被为托管代码,因为CLR管理它到底执行。编译器编译源代码时生成的代码。在运行,CLR将IL编译成本机CPU指令 |
二、程序集执行过程
IL代码是与CPU无关的机器语言,当方法执行时,必须把方法的IL转换成本机CPU命令。下图是方法的首次调用
在执行Main方法前,CLR会分配一个内部数据结构来管理对引用类型的访问,在这个内部数据结构项中,存在与类型中方法对应的一个记录项,这个记录项含有一个地址,可根据此地址找到方法的具体实现。数据结构初始化时,CLR将每个记录项都设置成包含在CLR内部的JITComplier。上图中的WriteLien首次调用时,JITCompiler会被调用,IL将被编译成CPU指令(JIT,即时编译)。当WriteLine第二次调用时,会跳过JITCompiler,直接执行CPU中已有的指令。(注:之前有经常遇到过首次调用某类方法明显比第二次的耗时长,应该与此有关联,因此有时候需要做预加热)。 下图为第二次调用
生成程序集时,有两种模式:Debug和Release。显然,根据词义可知分别是调试模式和发布模式。两者主要区别在于两个C#编译器开关会影响代码优化:/optimize和/debug。Debug含有DEBUG常量,以及不优化代码。Release不包含DEBUG常量,并优化代码。因此Release模式下的程序集相对而言更小,运行更快。
尽管微软做了大量的工作来优化,但运行时的二次编译仍会影响性能,也会分配动态内存。但是.NET FrameWork SDK 配套提供了NGen.exe工具,该工具将所有IL代码编译成本机代码,并将这些本机代码保存到了一个磁盘文件中。NGen.exe能够:1、提高应用程序的启动速度;2、减小应用程序的工作集。纵然NGen.exe工具能够带来不少好处,却并非那么完美,存在如下问题:1、没有知识产权保护 2、NGen生成的文件可能失去同步 3、交叉的执行时性能。因此,在使用NGen.exe工具时要慎重考虑,除非是启动耗时的大型客户端应用程序。
三、Framework类库
.Net Framework包含Framework类库(Framework Class Library,FCL)。FCL是一组DLL程序集的同城,其中包含有数千个类型定义。例如:WEB服务、基于HTML的web窗体/MVC应用程序、“富”WIndows GUI应用程序,Windows控制台应用程序,Windows服务,数据库存储过程,组件库等
四、通用类型系统和公共语言规范
为了令一种编程语言写的代码能与用另一种编程语言写的代码沟通,微软制定了一个正式的规范来描述类型的定义和行为,这既是“通用类型系统”(Common Type System,CTS)。CTS规定,一个类型可以包含零个以上的成员:字段、方法、属性、事件,CTS还指定类型可见性规则以及类型成员的额访问规则:private、family(protected,派生类型访问)、assembly(同一个程序可访问)、public。同时,CTS规定所有类型必须继承System.Object。CTS还有其他规则,不一一列出。
微软定义了“公共语言规范”(Common Language Specification,CLS),详细定义了一个最小功能集,用于不同语言创建的对象可通过COM相互通信,任何支持CLR的编译器都支持这个功能集。
注:程序集执行过程图来源自网络