今天在一场“特殊的讨论”中引入了一个问题,如何在C#求出字符串中某字符的出现次数,比如求“ADSFGEHERGASDF”中“A”出现的次数。首先想到的方法当然是从头遍历字符串并统计:
c1 = 0;
for (int i = 0; i < str.Length; i++)
{
if (str[i] == 'A')
{
c1++;
}
}
第二种方法也很容易想到,将字符串中所有要查找的字符去除,然后比较去除前后的字符串长度即可。这种方法遭到了某人的鄙视,据说性能很差而且多占空间。
c2 = str.Length - str.Replace("A", String.Empty).Length;
接下来某人又提出了第三种方法,是用要查找的字符为分隔符,将原字符串分隔为多个子串,然后求子串的数目即可。在C#中这是一个写起来很短的方法:
c3 = str.Split(new char[] { 'A' }).Length - 1;
我们从原理可以推断出三者性能的顺序,但究竟差距是多少呢,还是要动手试验一下。这是非常经典的测试代码:
string str = "SADTHDGSAFSDGTGHRDGSADFADDRHDFSGASDAA";
Stopwatch sw = new Stopwatch();
long t;
int c = 0;
GC.Collect();
Application.DoEvents();
sw.Start();
for (int i = 0; i < 100000; i++)
{
c = 三种算法
}
sw.Stop();
t = sw.ElapsedMilliseconds;
首先我们确保正确性,经测试三种方法都能正确处理多种情况,包括首尾、连续出现、不出现或串长度为0等,我所取的字符串是一个很普通的串。编译为Release版,预运行10次后获得以下结果:
遍历统计:13毫秒
替换后比较长度:112毫秒
断开字符串后计数:233毫秒
这里已经体现出差异,遍历统计比替换后比较要快10倍,断开字符串又要慢一些。接下来我又做了如下两个测试:
1、不改变字符串的长度,增加或减少要查找字符串的个数。
2、不改变要查找字符出现的频率,但增长字符串的长度。
结果发现,三种方法都随字符串长度增加线性变慢,而后两种方法还随要查找的字符增加而变慢。断开字符串的方法还受要查找字符串分布情况的影响。
研究Replace函数和Split函数的实现可以彻底解决这个问题。不过我没有心情细细研究了,我还是决定选用第二种方法——替换后比较长度。虽然其速度比第一种方法慢,但易于改写为求长度不为1的子串出现次数的方法。第一种方法若改为求长度大于1的字串就要考虑很多因素(尽管不一定真的很麻烦),我懒得想了,呵呵。
用过VB6的人都会对控件数组念念不忘,因为控件数组在处理多个控件统一事件上确实很方便。.NET Framework没有引入控件数组这一概念,这是因为.NET Framework的类型系统很完善,可以实现控件数组原来的功能。只是这样凭空增加了一些麻烦,我们不是需要在Handls字句后面写一长串控件名称,就是要用写数遍AddHandler或C#的+=语句给多个控件绑定同一个事件。为了减轻没有控件数组日子的痛苦,我写了这个替代方案:共享事件容器。在编写它的过程中,我用了泛型,但其实不用泛型也可以做到,只是泛型能够约束容器内控件的类型,减少可能的运行时错误。
在看实现方法之前,先来看看共享事件容器是怎么操作的。首先你先要放一些相同类型控件到窗体上,这些就是你的控件数组元素了,假设是button1,button2和button3。然后在窗体的构造函数或Load事件中添加如下代码:
ba = New ShareEventsContainer(Of Button) '创建一个共享事件容器
ba.AddRange(New Button() {Button1, Button2}) '用AddRange可以添加多个控件
ba.Add(Button3) '当然Add方法也是支持的了
这就是共享事件容器了,就像是一般的集合一样,你可以用数组的语法操纵其中的对象。接下来就是重头戏了,我们要将容器内的全部控件的Click事件都绑定到同一个方法上,比如这个方法
Public Sub Buttons_Click(ByVal sender As Object, ByVal e As EventArgs)
MsgBox("Hello! I'm " & CType(sender, Button).Name)
End Sub
则接着在刚才初始化共享事件容器语句的后面写事件绑定代码:
ba.AddHandler("Click", New EventHandler(AddressOf Buttons_Click))
有点像VB的语法,其实我就是取了个神似。这里事件名称是要用字符串表示的,可别写错了。而事件处理程序的委托也与一般的事件绑定语句不一样,这时必须将委托类型写出来,还得写正确了。VB和C#2.0的委托类型推定在这时是不起作用的。你还可以写更多的AddHandler,绑定其他事件。
好了,就这么简单,事件已经绑定好了。更进一步的是,将来继续往这个容器内添加新的按钮,他们的事件也会自动绑定到这时预定的所有处理程序上,而从容器中移除控件,其事件会自动解除绑定。你还可以在任意地方用AddHandler和RemoveHandler方法添加和删除新的事件处理程序,支持多播事件方法。
如果你觉得这个方法值得用用,那就可以看下面的实现代码了:VB2005 BETA1
Imports System.Collections.Generic
Imports System.Reflection
'包含委托的列表类型
Imports DelegateList = System.Collections.Generic.List(Of System.MulticastDelegate)
''' <summary>
''' 提供一个共享事件容器,容器内的控件可以共享事件处理过程
''' </summary>
''' <typeparam name="T">控件的类型,不支持Menu</typeparam>
''' <remarks>
''' 共享事件容器在事件处理上类似于Visual Basic 6.0或更早版本的控件数组,
''' 它可以将多个控件的事件绑定到同一个处理过程上,方便进行功能一致的操作。
''' </remarks>
Public Class ShareEventsContainer(Of T)
Inherits Collection(Of T)
'保存当前共享事件容器所预定的全部事件
'为了支持多播事件,必须采用这种结构的存储方式
Private events As Dictionary(Of String, DelegateList)
Private controlType As Type
''' <summary>
''' 初始化一个默认的共享事件容器
''' </summary>
Public Sub New()
MyBase.New()
controlType = GetType(T)
events = New Dictionary(Of String, DelegateList)
End Sub
''' <summary>
''' 初始化一个共享事件容器,并添加初始的控件
''' </summary>
''' <param name="controls"></param>
Public Sub New(ByVal ParamArray controls() As T)
MyClass.New()
If controls IsNot Nothing Then
AddRange(controls)
End If
End Sub
''' <summary>
''' 为容器内所有控件绑定统一的事件处理过程
''' </summary>
''' <param name="eventName">要绑定的事件名称,应当是T类型所支持的事件</param>
''' <param name="handler">对应于事件,并且类型正确的委托变量</param>
Public Sub [AddHandler](ByVal eventName As String, _
ByVal handler As MulticastDelegate)
If events.ContainsKey(eventName) Then
events(eventName).Add(handler)
Else
events.Add(eventName, New DelegateList)
events(eventName).Add(handler)
End If
Dim ev As EventInfo = controlType.GetEvent(eventName)
Try
For Each item As T In Me
ev.AddEventHandler(item, handler)
Next
Catch ex As NullReferenceException When ev Is Nothing
Throw New ArgumentException("所请求的事件不存在", "eventName")
Catch ex As ArgumentException
Throw New ArgumentException("事件处理的程序与事件的委托类型签名不符", "handler")
End Try
End Sub
''' <summary>
''' 为容器内所有控件解除绑定统一的事件处理过程
''' </summary>
''' <param name="eventName">要绑定的事件名称,应当是T类型所支持的事件</param>
''' <param name="handler">对应于事件,并且类型正确的委托变量</param>
Public Sub [RemoveHandler](ByVal eventName As String, _
ByVal handler As MulticastDelegate)
If events.ContainsKey(eventName) Then
events(eventName).Remove(handler)
Dim ev As EventInfo = controlType.GetEvent(eventName)
Try
For Each item As T In Me
ev.RemoveEventHandler(item, handler)
Next
Catch ex As NullReferenceException When ev Is Nothing
Throw New ArgumentException("所请求的事件不存在", "eventName")
Catch ex As ArgumentException
Throw New ArgumentException("事件处理程序与事件的委托类型签名不符", "handler")
End Try
End If
End Sub
'在删除项目时,解除该项目的事件绑定
Private Sub WhenRemoveItem(ByVal index As Integer)
Dim ev As EventInfo
For Each eventName As String In events.Keys
ev = controlType.GetEvent(eventName)
For Each handler As MulticastDelegate In events(eventName)
ev.RemoveEventHandler(Me(index), handler)
Next
Next
End Sub
'在插入新项目时,为该项目绑定所有事件
Private Sub WhenInsertItem(ByVal index As Integer, ByVal item As T)
Dim ev As EventInfo
For Each eventName As String In events.Keys
ev = controlType.GetEvent(eventName)
For Each handler As MulticastDelegate In events(eventName)
ev.AddEventHandler(item, handler)
Next
Next
End Sub
Protected Overrides Sub RemoveItem(ByVal index As Integer)
WhenRemoveItem(index)
MyBase.RemoveItem(index)
End Sub
Protected Overrides Sub ClearItems()
For i As Integer = 0 To Me.Count - 1
WhenRemoveItem(i)
Next
MyBase.ClearItems()
End Sub
Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As T)
WhenInsertItem(index, item)
MyBase.InsertItem(index, item)
End Sub
Protected Overrides Sub SetItem(ByVal index As Integer, ByVal item As T)
WhenRemoveItem(index)
WhenInsertItem(index, item)
MyBase.SetItem(index, item)
End Sub
''' <summary>
''' 添加多个项目到共享事件容器中
''' </summary>
''' <param name="items">包含要添加项的可列举容器</param>
Public Sub AddRange(ByVal items As IEnumerable(Of T))
For Each item As T In items
Add(item)
Next
End Sub
End Class
其实我写的也有些仓促,还没有仔细测试,估计还有不少毛病。不过这个思路的确可以简化不少使用控件数组方面的麻烦,我今后也会不断改进和完善它的。
又度过了繁忙的一年,人也又老了一岁。今天的生日也没有认真过,白天都在进行培训,晚上实在没力气,买个游戏玩玩吧。
实在没什么好写的,明天但愿有力气写篇技术文章……
为了了解Visual Basic和C++/CLI最新的变化,我安装了Visual Studio 2005 十月技术预览版(CTP)。Visual Basic变化并不大,增加了Code Expansion功能和泛型的引用类型/值类型约束。而C++/CLI则十分莫名其妙。IDE的智能感知全没了,全部都要凭记忆写。语法更加令人不解,IDisposable的Dispose方法被视为不可从代码中直接调用,而必须用delete语法调用Dispose,真是@!#$%&^%。而且我的Managed DirectX系列程序竟然不能编译了,提示是:
Error 1 error C2535: 'Microsoft::DirectX::Direct3D::VertexBuffer::~VertexBuffer(void)' : member function already defined or declared e:\My Documents\Visual Studio\Projects\MDXTest2\MDXTest2\DirectXProgram.h 40
还有
Error 3 error C2535: 'Microsoft::DirectX::Direct3D::Device::~Device(void)' : member function already defined or declared e:\My Documents\Visual Studio\Projects\MDXTest2\MDXTest2\DirectXProgram.h 80
等等,即Device、VertexBuffer等类型都无法创建实例,只要一创建就会有这种错误。这Device和VertexBuffer又不是我编写的,什么~Device已经定义过了,简直是胡说八道嘛!
不管怎么说,这个版本的C++/CLI是没法用的了,我的Managed DirectX学习笔记也要暂时改用Visual Basic和C#来进行了(也好,所有的语言都用用)。由于要重写实验项目,所以今天是写不出来了,过两天继续。
今天收到了这个“特别礼物”——印有Windows® Media标志的Creative MP3 。

它同时还是个U盘,只要从电池盒上拔下来即可,有120M的实际可用空间。经试验,对CBR和VBR的Windows Media格式支持得都不错,虽然音质不怎么样(也许是我用的WMA太烂了),但至少不像我以前见过的某一台低档MP3那样断断续续。那个耳机可不太好,还好我有一个PX100可以拿过来用用。按钮很少,除了开关音量,还有一个假滚轮可以前进和后退。同时这MP3还有一个LCD屏(支持中文!翻过来一看Made in China,怪不得-_-b,算了反正也不错了)。其他花哨的功能就没多少了。
还有不少人已经收到或者即将收到了吧。感谢Grace和微软公司带给我们这一“特别礼物”。