読み込んでいます...
2009年09月11日

问题:

给定一个盛有一些黑色豆子和一些白色豆子的咖啡罐以及一大堆额外的黑色豆子,重复以下过程,直至罐中仅剩一颗豆子为止。

从罐中随机选取两颗豆子,如果颜色相同,就将它们都扔掉并且放入一个额外的黑色豆子,如果颜色不同,就将白色的豆子放回罐中,而将黑色的豆子扔掉。

证明该过程会终止。最后留在罐中的豆子颜色与最初的罐中的白色豆子和黑色豆子的数量有什么数学关系。

思考:留在后面。

抽象:

1.用链表,但是不适合随机查找,不适合用数组。(C#可以用List)

2.黑白豆子用0,1表示。

3.随机数,两次随机拿一个豆子,就是两次随机。

技巧:留在后面。

明确了问题和抽象之后,我们可以编写一下代码来实现这个咖啡豆问题。如果你懒得看代码直接看后面的思考和技巧。

//定义一个List泛型对象
private static List<int> box;

static void Main(string[] args)
{
    
//初始化4个黑豆子和4个白豆子,找规律
    TestMain(4, 4);
    
    Console.ReadKey();
}

static void TestMain(int blackNumber,int whiteNumber)
{
   
//初始化“咖啡罐”
    box = new List<int>();
    SetBox(blackNumber,whiteNumber);
   
//如果“咖啡罐”里面的咖啡豆不等于1
    while (box.Count != 1)
    {
       
//拿咖啡豆
        GetCoffee();
    }

    Console.WriteLine(GetStyle(box[0]));
    box
= null;
}

上面我们写一个主函数,这个代码比较简单,虚拟了拿咖啡豆的过程,如果咖啡豆的数量不等于1的话,我们就继续拿,这里用了SetBox方法初始化咖啡罐,代码如下。

//0 黑色咖啡豆
//1 白色咖啡豆
static void SetBox(int blackNumber, int whiteNumber)
{
    Console.WriteLine(
"Black " + blackNumber + " White " + whiteNumber);
    
//添加黑色豆子
    for (int i = 0; i < blackNumber; i++)
    {
        box.Add(
0);
    }

    //添加白色豆子
    for (int i = 0; i < whiteNumber; i++)
    {
        box.Add(
1);
    }
}

这里最主要的方法就在GetCoffee方法,这个是我们的核心方法,在明确了问题和抽象之后我们可以写如下代码,代码并不复杂。

static void GetCoffee()
{
    
//拿出的咖啡豆的颜色初始化
    int fCoffee = 0;
    
int sCoffee = 0;
    
    
//随机数
    Random random = new Random();

    //随机数的最大值
    int max = 0;
    
    
//随机数的最大值等于咖啡罐中的咖啡豆的数量
    max = box.Count;

    //随机生成两个index
    int fIndex = random.Next(0,max);
    
int sIndex = random.Next(0,max);

    //如果不是同一个index
    
//这里如果是同一个再执行的话肯定是有错误的
    if (fIndex != sIndex)
    {

        //分别获取咖啡豆的颜色
        fCoffee = box[fIndex];
        sCoffee
= box[sIndex];

        //如果颜色相同
        if (fCoffee == sCoffee)
        {
            
//丢掉一个咖啡豆
            box.RemoveAt(fIndex);
            
//这里链表或List的长度变短,要检查是否只剩一个了
            if (box.Count != 1)
            {
                
//如果还有,后面的索引减少一个
                if(sIndex!=0)
                {
                    sIndex
;
                }
                
//移除咖啡豆
                box.RemoveAt(sIndex);
                
//添加一个黑色豆子
                box.Add(0);
            }
            
else
            {
                
//添加黑色豆子
                box[0] = 0;
            }
        }
        
else
        {
            
//颜色不相同检查是否为白色
            
//如果不是白色的话就丢掉
            if (!IsWhiteCoffee(box[fIndex]))
            {
                box.RemoveAt(fIndex);
            }

            if (!IsWhiteCoffee(box[sIndex]))
            {
                box.RemoveAt(sIndex);
            }
        }
    }
}

嗯,上面的逻辑比较复杂,其实也比较简单了,注释都写在上面了,读者可以自行查看,这里IsWhiteCoffee函数很简单,代码如下。

static bool IsWhiteCoffee(int color)
{
    
return color==1;
}

嗯,整个问题来说不难,不过这里有两个陷阱。

  1. 两个相同颜色的咖啡豆。
  2. 随机拿取

两个相同颜色的咖啡豆这个地方,值得注意的是,如果你的“咖啡罐”中本来就只有2个白色豆子,那么你拿出来的就是2个相同颜色的豆子,那么如果你先Remove链表里的一个元素,那么第二个再Remove的话,那么索引就超出范围了,这就会有异常。而且题目也没说这个咖啡罐里面一定两种颜色都用,说不定都是白色的咖啡豆呢。

第二个随机拿取这里就比较有意思了,具体分析一下。

思考:

其实在做题的时候我们应该先思考,这也就是为什么这个题目还小有意思,大部分的人都会一上来就看题目然后写代码,最后看看结论比较的对不对,这不是一个好习惯。OK,我们来想想这个问题,恩,如果两个颜色不同,就丢掉黑色的,如果两个颜色相同,就都丢掉。仔细想想,仔细想想,恩,对,也就是说,无论我们怎么拿豆子,白色的豆子的取出的过程都是,2个,0个,2个,0个

也就是说,白色的豆子为偶数个的时候,留下的一定是黑色,而白色豆子为奇数的时候,留下的一定是白色!所以,随机不随机根本没有必要,就算你从第0个开始,顺序的拿两个豆子又怎样了呢?一样是这个结果,所以根本不用随机拿取。

技巧:

嗯,思考了之后,这个问题就太简单了,根本就不用浪费那么多时间写代码了,我们可以把随机过程改成顺序过程,或者直接干脆判断白色豆子的奇偶性,如果白色豆子为奇数,返回白色,否则为黑色。恩,改成一行代码了,简单吗?

总结:

所以说,写代码的时候,应该先思考,再写,否则会浪费很多精力,就算我们的程序的每个函数的性能都是O(1),还是不及一行代码来的快,毕竟程序,本来就是数学的程序。:)

详细数学推导过程请看我一个朋友的日志。

.NET在今天来说已经算是比较普及了,开发程序的时候,我也一直要求用户安装.NET框架,不过这个不会特别好,所以想做一些脱离.NET框架的东西。一直想做这个事情,不过一直没有做成,今天有点空写一下如何实现.NET应用程序脱离.NET环境运行,我们首先说一下.NET应用程序脱离.NET环境运行的实现方法,目前来说比较好用的方法有3种。

  1. 使用飞信虚拟环境。
  2. 使用MONO.NET。
  3. 使用.NET Linker。

首先说第一种,我们都知道飞信是C#开发的(我一个朋友还有源代码),但是不需要.NET环境,因为飞信用了虚拟环境,我们也可以用飞信的虚拟环境,不过比较麻烦,而且启动速度比较慢,而且还有版权问题。第二种是最好的方式,不仅启动速度比较快,而且还跨平台,支持Linux和Unix以及Mac OS,虽然那上面的.NET程序需求不高,但是如果做的产品好的话,还是不错的。最后一种不是很复杂,但是要破解,也有版权问题,启动速度比较慢。

所以这里我们就说MONO吧,MONO我相信大家都比较熟悉了,一个让.NET跨平台的公司,可惜微软赞助支持的不是很好,因为微软就只抱着Windows的态度,不过MONO还不错,坚持下来并且拉到了微软的赞助。现在的MONO已经对.NET 2.0支持的比较成熟,3.5也支持一些,但不是很多,反正国内环境来说2.0比较成熟,就用2.0也行。

首先我们下载一个MONO。下载完成后安装(这不是废话),安装到一个文件夹之后,比如D盘,就到D盘找到你安装的路径。

现在我们写一个.NET程序,写个最简单的命令行的吧,测试一下,我们拷贝到安装的路径下面,然后运行下面的代码。

bin\\mono.exe appname.exe

上面的appname是你的应用程序名称,OK,看看,是不是可以运行了,这个就是用MONO提供的程序跑.NET框架。

不过现在看上去比较大,我们可以精简一下,我们首先把程序拷贝到一个干净的目录里面,然后我们再把安装路径下的bin和lib拷贝到这个目录里。那么这个目录就有我们的程序,MONO的bin目录和MONO的lib目录,看看大小,嗯,300多M,太大了,所以我们得精简一下。

我们首先精简bin目录,bin只需要保存下面几个文件就可以了。

  • mono.dll
  • libgthread-2.0-0.dll
  • libglib-2.0-0.dll
  • intl.dll
  • iconv.dll
  • mono.exe

除了上面的文件以外我们都可以删除掉,然后我们到lib目录里(这里我是一个一个测试删除的。。=。=!),保留下面的文件就可以了。

  • mono文件夹

mono文件夹里面保留这些文件,如果你要使用多个版本的话,比如2.0和3.5的特性,可以保留相应的文件夹,这里我保留了2.0,3.5和gac,其中2.0和gac是必须的。

我们在2.0目录里面只需要保留以下文件。

  • Accessibility.dll
  • mscorlib.dll
  • mscorlib.dll.mdb

其他的可以狂删,在gac目录里面,我们只需要保留下面这个文件夹就可以了。

  • Boo.Lang

我们再来压缩一下我们的程序,程序大小才8m,可以接受了。

这里值得一提的是,我们在不同的平台里面可以使用不同的C++程序来启动我们的程序(即运行\\bin\mono.exe appname.exe),代码如下。

int main()
{
    WinExec(
"bin\\mono.exe appname.exe",SW_SHOWNORMAL);
    
return 0;
}

不同的平台可能C++代码不同,不过有另外一个比较好的方法,就是写批处理文件,这样的话无论在Windows里面还是在Linux里面,启动起来都比较容易,至少比写几个C++程序要简单的多吧。

327路过 0评论 DotNet 阅读全文..
2009年08月18日

前面我写了委托相关的知识,我们通常使用的过程中,经常会用到事件和委托,而我们大部分的原因都是知其然不知其所以然,为何要使用事件?既然有委托,好像就够了啊。我们可以看看下面的代码。

namespace DelegateTest
{
public delegate void SaySomething(string name);

public class Tester
{

public SaySomething saysomething;
public static void SayHelloTo(string name)
{
Console.WriteLine(
Hello + name);
}

public static void SayGoodByeTo(string name)
{
Console.WriteLine(
GoodBay + name);
}
}
}

我们写了委托,也写了相应的内容,但是上面的代码有几个问题,这几个问题是。

  • 委托是公有的变量,这样不好,很容易在外部被客户修改,前面我们的代码是可以工作,但是一点也不好。
  • 前面的代码我们为了是代码重用和可扩展,但是如果委托的修饰编程private之后,安全性是可以,但是破坏了OO面向对象的特性。

前面的代码中,我们看到可以叠加方法,例如前面的代码里面的saysomething()方法,可能会执行多次,如果用户在调用的时候+=多个方法,那肯定不是我们想要的,而且客户端可以随意修改委托,这就是属于严重的破坏了封装,也不是我们想要的,所以这个时候我们就需要event,event即是使用属性对委托字段进行了封装,提高了封装性。而不是修改类里面本身的东西。托这里我们写成下面的代码。

public class Tester
{
public event SaySomething saysomething;

public static void SayHelloTo(string name)
{
Console.WriteLine(
Hello + name);
}

public static void SayGoodByeTo(string name)
{
Console.WriteLine(
GoodBay + name);
}
}

我们可以看到这里我们将委托前面加了一个event关键字,我们就可以在公有的部分调用相应的委托(事件)了,代码如下所示。

namespace DelegateTest
{
class Program
{
static void Main(string[] args)
{
Tester test
= new Tester();
test.saysomething
+= new SaySomething(SaySomething);
test.saysomething(
test);
Console.ReadKey();
}

static void SaySomething(string name)
{
Console.WriteLine(name);
}
}
}

OK,好,我们现在来编译一下这个代码,发现编译出现出错,错误是【The event ‘DelegateTest.Tester.saysomething’ can only appear on the left hand side of += or -= (except when used from within the type ‘DelegateTest.Tester’)】,从这个错误我们可以看出,无论我们声明event的时候,无论是公有还是私有,其实被编译后都是私有的,所以会导致这个错误,所以这里我们要将事件和委托联系起来。

然后看看这篇文章

194路过 0评论 DotNet 阅读全文..

其实我前面写了一篇文章,讲了关于事件和委托的用法,其实事件和委托可能很难,但是对于初学者也很难,对于像我这样的菜鸟也很难。就像我的一位同事说的“玩的不要的”东西。但是我还是要说说委托,本来确实算是老生常谈的东西,但是我发现委托还是有很多用处的。比如最近我在弄设计模式以及代码重构的时候,就发现了很多可以使用委托的地方。

现在还是需求为先,我这里有个需求,向某人说Hello,OK,很简单,我们可以写下面的代码。

public static void SayHelloTo(string name)

{

Console.WriteLine(Hello + name);

}

OK,如果现在来了一个新的需求,说要对某人说Good Bye,那么我也可以再写一个SayGoodBye方法,OK,代码如下。

public static void SayGoodByeTo(string name)

{

Console.WriteLine(GoodBay + name);

}

好吧,现在我们要使用这两个方法了,我对某人说Hello,我可以这样使用。

public static void Main()

{

SayHelloTo(GuoJing);

}

如果有时候我们要说Hello,有时候要说GoodBuy的话,那不是要加一个Switch了?恩,对,我们可以实现下面的代码。

public static void Main(string type)

{

switch (type)

{

case Hello: SayHelloTo(GuoJing); break;

case GoodBye: SayGoodByeTo(GuoJing); break;

}

}

上面的代码很容易写,但是写的不好,老实说我也常犯这种错误,不过好在咱们是大好的红色热血青年,既然有写的不对的地方我们可以改进。上面的代码有几个地方可以改进,

  1. type最好是枚举,这样使用的人就不会传错参数。
  2. 方法的参数最好也可以传递进来,这样就减少了重复。
  3. default最好定义,或抛出异常。
  4. 代码重复。

不过上面的前3个可以改进的地方不是我们本期话题的重点,我们最主要的是第4个部分,代码重复。我们可以看到这个需求,如果要说点什么别的话,如Love之类的,我们就得写很多方法,例如写SayLoveTo,SayFuckTo,恩,这样很不好,这里很多人可能就会想到如果能把方法名称传递进去的话就好了,这样就可以减少代码的重用性。

OK,先写一个委托。

public delegate void SaySomething(string name);

声明了一个委托之后,我们再改进一下我们的Main方法,我们可以当参数传递进来,改进如下。

public static void Main(string name,SaySomething method)

{

method(name);

}

现在,我们可以将方法名称直接传递过来执行相应的方法了。我们知道了如何传递方法名字,现在就要知道如何声明这个方法的“变量”了。其实委托的实现就是类的一种实现方式,可以看作类或者类型,只不过是一种特殊的类或类型。我们可以这样声明一个委托。

public static void DoSomething()

{

//声明一个委托变量

SaySomething saysomething;

saysomething = SayGoodByeTo;

//传递参数到方法

Main(GuoJing, saysomething);

}

上面是声明了委托变量然后传递到Main方法中,我们运行之后,确实我们的程序写好了而且也运行成功了,不过感觉还是有点怪怪的,为什么。。恩,既然委托和方法是一个地位的话,而且委托也是一种特殊的类,我们应该可以使用new关键字吧,而且类也应该可以执行自己的方法,不错,我们可以试试。

public static void DoSomething()

{

//直接new一下

SaySomething saysomething = new SaySomething(SayHelloTo);

saysomething(GuoJing);

}

恩,运行了之后,发现代码还是对的,也可以运行,是不是很简单,减少了很多代码,而且代码也重用了很多,这样就可以重用很多东西,减少了复杂的类似switch之类的代码。

OK,不过委托还可以使用+=和-=关键字进行方法的叠加和减少,我们可以看一下下面的代码。

public static void DoSomething()

{

SaySomething saysomething = new SaySomething(SayHelloTo);

//增加了SayGoodBye方法的绑定

saysomething += SayGoodByeTo;

//去掉了SayHelloTo方法的绑定

saysomething -= SayHelloTo;

saysomething(GuoJing);

}

我们可以运行一下上面的代码,看看结果是什么,没错,结果就是Say Good Bye to GuoJing。这样,我们可以给一个委托绑定多个方法,并且执行多个方法。不过委托更多时候还是和事件联系在一起,可以到这篇文章看看如何联系事件和委托

事件和委托就和面向对象一样,我相信大部分人都非常清楚如何使用,但是很多人都并没有用到最好,我自己也很难用到最好,减少代码,提高重用性,提高维护性,这真是程序员一生研究的对象啊。

237路过 0评论 DotNet 阅读全文..

用了.NET很长时间,经常使用事件和委托,但是一直没有深入研究事件和委托的作用,因为在应用层面来说,.NET框架为我们提供了很多很好的事 件,所以我们大可不必自己去实现事件,但是在很多情况下,当我们重新编写类库或者编写类的时候,我们往往要自己定义事件,所以这里就需要写事件和委托并且 响应相应的方法,这里我还是用最经典的加热器的例子来举例说明我们的事件和委托吧。其实老生常谈的东西我也不想多写,就当纯属Mark吧。

看这篇文章前,请先看:

例如我现在这里有这样一个需求,这个需求就是加热器一直加热,加热到一定温度之后我就不加热了并且提示已经加热到了,不再会加热了,这个需求很简单,我们可以编写下面的代码。

int t = 0;

for (int i = 0; i < 10; i++)
{
    
//temp.Temp += 10;
    t += 10;
    Console.WriteLine(t);
    
if (t == 100)
    {
        Console.WriteLine(
"已加热完毕");
    }
}

当然上面的代码也能够很好的满足我们的需求,但是如果一旦加热的温度改变了怎么办?我们还得在代码里面t==100修改一下我们的温度,让然这里我 们也可以将100作为一个参数传递进来,这样也可以实现,但是并不好,因为如果我想修改一下这个方法的实现怎么办,那就要改代码,如果有多个参数的话,那 就需要更改更多的东西了,维护起来也更加麻烦,这里我们就要使用委托和事件。 

首先我们先创建一个StopEventArgs类,并且编写以下代码。

using System;
using System.Collections.Generic;
using System.Text;

namespace EventsTest
{
    
public class StopEventArgs
    {
        
private int _reached;

        public StopEventArgs(int num)
        {
            
this._reached = num;
        }
    }
}

我们可以看到这个命名和EventArgs一样,为了更加容易读,我们这里的参数也用XXXEventArgs进行命名,这也是微软推荐的命名方式。这个作为一个事件响应的参数而存在,我们现在编写一个Handler。下面是整个编码。 

using System;
using System.Collections.Generic;
using System.Text;

namespace EventsTest
{
    
public delegate void StopEventHandler(object sender,StopEventArgs e);

    public class Temperature
    {
        
private int _temp = 0 ;
        
private bool _isBolid;

        public event StopEventHandler eventHandler;

        /// <summary>
        
/// 响应事件的方法
        
/// </summary>
        
/// <param name="e"></param>
        protected virtual void OnTemperatureReached(StopEventArgs e)
        {
            
if (eventHandler != null)
            {
                eventHandler(
this, e);
            }
        }

        /// <summary>
        
/// 加热的方法
        
/// </summary>
        public virtual void Heater()
        {
            
if (Temp > MaxTemp)
            {
                
if (eventHandler != null)
                {
                    
if (!IsBolid)
                    {
                        StopEventArgs args
= new StopEventArgs(Temp);
                        OnTemperatureReached(args);
                        IsBolid
= true;
                    }
                }
            }
            
else
            {
                Console.WriteLine(Temp);
            }
        }

        /// <summary>
        
/// 温度属性
        
/// </summary>
        public int Temp
        {
            
get { return _temp; }
            
set { _temp = value; Heater(); }
        }

        /// <summary>
        
/// 是否已经加热成功属性
        
/// </summary>
        public bool IsBolid
        {
            
get { return _isBolid; }
            
set { _isBolid = value; }
        }

        /// <summary>
        
/// 加热的温度的属性
        
/// </summary>
        public int MaxTemp
        {
            
get;
            
set;
        }
    }
}

上面的代码我们申明了一个委托,其实这个委托叫什么名字参数都无所谓,但是这里我们遵守微软的命名规则,也让使用者更加方便的使用,我们就一样要用EventHandler。这里我们逐步逐步的分析代码。 

public delegate void StopEventHandler(object sender,StopEventArgs e);

然后我们在类中声明了一个事件,这个时间是使用的StopEventArgs类型。 

public event StopEventHandler eventHandler;

然后我们在基类编写相应事件的方法。 

protected virtual void OnTemperatureReached(StopEventArgs e)
{
    
if (eventHandler != null)
    {
        eventHandler(
this, e);
    }
}

这个地方大家应该已经很熟悉了,我们用虚方法编写事件响应方法,那么在用户可以继承这个方法,当触发了事件之后,我们可以进行不同的处理,这样就更具有灵活性和扩展性。 

但是我们这里的事件和系统事件很不同,我们必须要写一定的逻辑来初始化StopEventArgs,因为系统的事件如Click都会在背后初始化好,我们可以不必关心这个地方,但是我们自定义事件的时候,还需要初始化我们的事件的参数。我们编写了以下逻辑。 

public virtual void Heater()
{
   
//如果温度达到了指定温度
    if (Temp > MaxTemp)
    {
       
//如果事件已经被注册了
        if (eventHandler != null)
        {
           
//如果还不是保温状态了
            if (!IsBolid)
            {
                StopEventArgs args
= new StopEventArgs(Temp);
                OnTemperatureReached(args);
                IsBolid
= true;
            }
        }
    }
    
else
    {
        Console.WriteLine(Temp);
    }
}

也就是当我们的温度到达指定的温度之后,我们就触发相应的事件,执行相应的方法。这样整个类的编码也分析完了。 

编写完成后,我们就可以在主方法中调用这个类并注册相应的事件了。 

class Program
{
    
static void Main(string[] args)
    {
        Temperature temp
= new Temperature();
        temp.MaxTemp
= 90;
        //注册事件
        temp.eventHandler += new StopEventHandler(OnReached);

        for (int i = 0; i < 10; i++)
        {
            temp.Temp
+= 10;
        }

        Console.ReadKey();
    }

    static void OnReached(object sender,StopEventArgs e)
    {
        Console.WriteLine(
"已加热完毕");
    }
}

我们初始化了一个温度计对象并且初始化了温度属性,然后我们注册了事件并在事件中编写了相应的方法,运行之后我们可以看到结果如下。

这样我们就可以看到当温度高于90度的时候,我们的温度计就告诉我们加热完毕了,这样就可以停止加热了。这里可能还是对事件响应的方法为什么要用虚 函数进行编写,因为这里我们使用虚函数的话,可以通过派生类重写事件的相应过程,例如我们新建一个Heater类,可以编码如下。

namespace EventsTest
{
    
public class Heater : Temperature
    {
        
private int _temp = 0;

        public Heater()
        {
            
this.MaxTemp = 70;
        }

        //重写响应事件的过程
        protected override void OnTemperatureReached(StopEventArgs e)
        {
            Console.WriteLine(
"Heater");
            
base.OnTemperatureReached(e);
        }
    }
}

我们就可以看到结果如下。

小结:

这里有几个细节。

1.我在设置属性的时候就使用了相应的方法,其实在属性改变的时候就在内部调用了方法。这里可以在外部修改。

2.你会发现如果你不注册事件的时候,这里的代码到制定温度还是会停下来,因为温度超过之后我就没输出温度了。

3.从2里面你会发现,这里我说的只是响应事件的过程,例如温度到了90度之后,就会响应事件,执行方法,和输出没有关系。

4.2,3的问题可以加一句话就可以修改,可在源代码里看。

5.为何要用object sender和eventargs去声明方法名,因为不仅仅是命名规范,也可以通过sender获取传递过来的对象并进行信息的扩展。

下载源码

EventsTest.rar (26.21 kb)

283路过 1评论 DotNet 阅读全文..

最近在学习一些基础,当然平时也在做一些有趣的事情,基础有很多方面,最最基础的算是数据结构和算法了,这些都在慢慢的积累(谁叫大学的时候没有学好ToT),然后就是看.NET的一些基本的东西,最近就在看一些关于IL的基本知识,对于了解一些基本类型和概念都很重要,现在我们也开始看看IL。

任何书籍开始都是写Hello World,咱都是俗人,所以也不能免俗,我们写一个简单的Hello World程序,方便我们第一次来看IL,示例代码如下所示。

namespace Hello
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(
Hello World);
Console.ReadKey();
}
}
}

上面的代码很简单,就是输出一个Hello World,OK,我们的东西输出了之后,就可以通过Windows SDK里面IL反汇编工具来进行反汇编了。我们打开IL反汇编工具打开我们相应的exe文件,我们可以看到下面的窗口。


上面就是我们程序的一个基本的IL的反映了,我们可以一个一个文件的查看,首先查看MANIFEST文件,双击可以看到如下代码。

// Metadata version: v2.0.50727
.assembly extern mscorlib
{
.publickeytoken
= (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}

.assembly Hello
{
.custom instance

void
[mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(
string)
= ( 01 00 05 48 65 6C 6C 6F 00 00 )
.custom instance void
[mscorlib]System.Reflection.AssemblyDescriptionAttribute::.ctor(
string)
=
( 01 00 00 00 00 )
.custom instance
void
[mscorlib]System.Reflection.AssemblyConfigurationAttribute::.ctor(
string)
=
( 01 00 00 00 00 )
.custom instance
void
[mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(
string)
=
( 01 00 00 00 00 )
.custom instance
void
[mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(
string)
=
( 01 00 05 48 65 6C 6C 6F 00 00 )
.custom instance void
[mscorlib]System.Reflection.AssemblyCopyrightAttribute::.ctor(
string)
=
( 01 00 12 43 6F 70 79 72 69 67 68 74 20 C2 A9 20
20 32 30 30 39 00 00 ) //就是定义程序名,版本,版权等,更多的就不重复了..
.hash algorithm 0×00008004
.ver
1:0:0:0
}

.module Hello.exe

// MVID: {62500A21-5A70-423F-9EB4-B12911149766}
.imagebase 0×00400000
.file alignment
0×00000200
.stackreserve
0×00100000
.subsystem
0×0003 // WINDOWS_CUI
.corflags 0×00000001 // ILONLY
// Image base: 0x01E90000

上面的文件看起来好像很复杂一样,但是其实并没有我们想象的那样复杂,我们可以从文件上面到下面慢慢的开始看。

首先我们看到的是【.assembly】,该指令用于定义编译目标和使用的外部库,我们可以从代码中可以看出【.assembly extern mscorlib】是调用了外部库mscorlib,而我们的编译目标则是【.assembly Hello】,这里我们可以看到mscorlib属于一个非常核心的库,这个库在任何的.NET托管的代码上来说基本上都是要使用的。在【.assembly】指令钟,我们还可以看到【.publickeytoken】指令和【.ver】,这两个指令都是定义程序集的唯一标示的,和我们的GUID达到的效果差不多。

另外在定义编译目标的指令时,我们也可以看到里面有一个【.hash algorithm】指令,改指令是定义hash的算法,缺省时SHA1算法,而ver则是我们程序集的版本号了。另外在文件尾部还有一些指令,这些指令分别是,imagebase为影像基地址,.file alignment为文件对齐数值,.subsystem为连接系统类型,0×0003表示从控制台运行,.corflags为设置运行库头文件标志,默认为1。这些指令以后有空再说。

然后我们就可以看Hello World类了,IL代码如下所示。

.class private auto ansi beforefieldinit Hello.Program extends [mscorlib]System.Object
{
}
// end of class Hello.Program

从上面的代码来说,基本上和C#很相似了,但是还是要说明一下,class表示这是一个类,private表示的是访问类型,auto表示程序加载内存的局部是由CLR决定还是由程序自己决定,ansi则是在托管代码和非托管代码下进行转换,而beforefiledinit则是为方法提供了一个附加描述,在CLR编译的时候会考虑是否进行优化,如果没有则不会优化可能影响性能。

接下来就是.ctor代码了,代码如下所示。

.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
// 代码大小       7 (0×7)
.maxstack 8
IL_0000:  ldarg.
0
IL_0001:  call       instance
void [mscorlib]System.Object::.ctor()
IL_0006:  ret
}
// end of method Program::.ctor

我们可以看到这里是一个方法体,【hidebysig】表示如果这个方法为父类,则不允许被继承,而这里着重要指出的是【cil managed】说明了这个方法是在IL里被托管的,有助于编译器识别。然后我们可以看到这里由一些指令,【.maxstack】是指执行该方法时所计算的最大堆栈。IL_0000一般都是起始行,一般都用于部分变量的初始化,ldarg表示装载的第一个成员参数,具体做什么的我还没弄清楚。。接下来就是【call】指令,表示调用一个静态方法,我们可以看到这里调用了mscorlib里的方法,ret表示执行完毕,返回。

最后就是Main方法了,我们可以看到Main方法的IL代码如下所示。

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小       19 (0×13)
.maxstack 8
IL_0000:  nop
IL_0001:  ldstr
Hello World
IL_0006:  call
void [mscorlib]System.Console::WriteLine(string)
IL_000b:  nop
IL_000c:  call       valuetype [mscorlib]System.ConsoleKeyInfo
[mscorlib]System.Console::ReadKey()
IL_0011:  pop
IL_0012:  ret
}
// end of method Program::Main

这里的代码和前面的IL的代码看上去非常相似对吧,所以我也不多说了,唯一几个要说的是这里的【.entrypoint】就是在运行这个exe的时候,程序会首先从这里执行。【nop】是空操作指令,【ldstr】是压栈指令,我们可以看到现在将参数压栈(WriteLine也是方法),然后调用了外部的静态方法WriteLine,我们就可以通过IL看到程序的一个方法是如何压栈的。最后【pop】出栈并返回。

从前面的代码可以看出,IL指令并不复杂,在一定程度上能够帮助我们了解更多关于.NET基础的东西,但是是否非常值得学习和学了之后是否很NB我就保持中立了,因为毕竟也不是很很很复杂,而且很多时候DEBUG也不需要完全使用IL。但是对IL有一定的了解,肯定对.NET本身有着更多的好处。

346路过 1评论 DotNet IL 阅读全文..

今天实在是忍不住了,觉得Blog Engine的编辑器实在的是太烂了,好在我对HTML还比较熟练,弄不了的话还可以直接写代码,今天遇到一个插件的超级大BUG,就是不能贴代码,贴代码的时候一篇文章只能贴一类的,要不就是HTML要不就是C#,对于我这种需要用多种源代码的人来说,肯定是不行的了,回家了以后就开始着手开发FCKeditor高亮插件,这个插件是在木子的博客上增加过来的,不过可惜木子的插件已经不能用了,有些资源也过时了,现在只能自己动手,丰衣足食了。

首先我们下一个最新的FCKeditor,然后把fckeditor目录上传到博客的根目录。首先,我们在FCKeditor/editor/plugins目录下新建一个insertcode目录,并在insertcode目录下新建一个fckplugin.js文件。并在文件中编写如下代码。

FCKCommands.RegisterCommand(InsertCode, new FCKDialogCommand(InsertCode, FCKLang.InsertCode, FCKPlugins.Items['insertcode'].Path + insertcode.aspx, 700, 600)) ;
var insertcodeItem = new FCKToolbarButton(InsertCode, FCKLang['InsertCode']) ;
insertcodeItem.IconPath
= FCKPlugins.Items['insertcode'].Path + images/insertcode.gif;
FCKToolbarItems.RegisterItem(
InsertCode, insertcodeItem);

在FCKeditor/editor/plugins/insertcode目录下创建images,lang,languages目录,在lang目录下新建en.js。en.js的内容为:FCKLang.InsertCode   = ‘Insert Codes’ ;

下载CodeHighlighter控件并解压,把CodeHighlighter/bin目录下的三个DLL复制到bin目录,将CodeHighlighter/Languages里的Lexers整个目录复制到FCKed

今天在做一个项目的时候,遇到了这样一个问题,就是我们现在有两个用户控件,一个是选择时间,另一个是执行事件。本来页面好好的,后来来了一个需求说要一个控件的按钮能够激发另一个控件的事件。当然了,这个我没做过,也觉得挺麻烦的,所以只好google一下,可惜google得出来的结论让我非常失望,大部分都是引用另一个控件,然后在另一个控件中用FindControl方法找到控件然后再执行。

我试了一下,在母版页下根本就不能运行,晕死,而且还没个网站都转载那篇文章,看来根本就没有搞清楚根本的概念。其次,就算是可以,也不能在母版页中使用,所以说这个方法根本就是没有用的,也不是很好扩展的,研究了一下,写了另一个方法,用事件和委托来做。可以扩展到不同的方法并直接使用自定义控件中的对象。效果如下所示。

首先我们先创建一个母版页,代码很简单。

<body>
    
<form id="form1" runat="server">
    
<div>
        Master page
        
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">      
        
</asp:ContentPlaceHolder>
    
</div>
    
</form>
</body>

然后我们再创建两个自定义控件,分别为WebUserControl1.ascx何WebUserControl2.ascx,其代码分别如下所示。

WebUserControl1.ascx

<asp:Button ID="Button1" runat="server" Text="Button" onclick="Button1_Click1" />

WebUserControl2.ascx

<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>

我们可以看到这个代码很简单,第一个控件是一个按钮控件,第二个控件是一个Label控件,我们所要达到的目的就是按第一个控件的按钮之后,第二个控件中Label控件的文字就会更改。首先,我们首页要添加这两个控件,代码如下。

<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1"
runat
="server">
    
<div class="test">
    
<uc1:WebUserControl1 ID="WebUserControl11" runat="server" />
    
<br />
    
<uc2:WebUserControl2 ID="WebUserControl21" runat="server" />  
</div>

不是很困难吧,首页就是调用了两个控件,现在我们为这两个控件能够彼此调用添加事件。

WebUserControl1.ascx.cs

public delegate void DelPassText (string s); //申明一个委托
public partial class WebUserControl1 : System.Web.UI.UserControl
{
    
public void Page_Load(object sender, EventArgs e)
    {

    }

    public event DelPassText notise; //申明一个事件

    
protected void Button1_Click1(object sender, EventArgs e)
    {
        
if (notise != null)
        {
            notise(
"test");
        }
    }
}

WebUserControl2.ascx.cs 

public partial class WebUserControl2 : System.Web.UI.UserControl
{
    
protected void Page_Load(object sender, EventArgs e)
    {

    }

    public void receive(string s) //这里就是接收的方法了
    {
        Label1.Text
= s;
    }
}

OK,我们两个控件的事件和彼此关系的代码已经OK了,现在就看看在首页怎么去写吧,只要注册一下事件就可以了,示例代码如下。

protected void Page_Load(object sender, EventArgs e)
{
    ContentPlaceHolder contentPlaceHolder
= Master.FindControl("ContentPlaceHolder1")
    
as ContentPlaceHolder;

    WebUserControl1 wc1 = contentPlaceHolder.FindControl("WebUserControl11")
    
as WebUserControl1;

    WebUserControl2 wc2 = contentPlaceHolder.FindControl("WebUserControl21")
    
as WebUserControl2;

    wc1.notise += wc2.receive;
}

注册之后我们运行一下,就能够达到我们的效果了。

小结

这里有两个比较重要的知识点也是基础,如果不太理解的话会画很多很多的时间。

1.是控件之间的通信,也是最基础的面相对象中的事件和委托,这是基础。

2.就是模板页载入和访问对象的问题,在使用FindConrol方法的时候,如果没有模板页,直接使用Page.FindControl是没问题的,如果在模板页的话的,就必须先找到ContentPlaceHolder才能再找到下面的控件,这也是基础。

所以说掌握基础是非常重要的。

代码下载

WebApplication1.rar (25.82 kb)

275路过 1评论 DotNet 阅读全文..

很早之前用C#写一个log processer出了一点问题,这个程序很简单,就是把一些日志文件给导入到数据库里面去,然后在对比日志中的字符串是不是我们想要的,说明白了就是读文件然后用正则表达式去匹配。然后当我们开始写的时候就发现其实并不是那么容易的事情,因为每次运行了超过1,2个小时的时候,就会造成内存过高,连我的开发用的机器的4G内存都内存不够,导致系统强制的清理内存。这个原因找了很长时间,但是都没有找到。那个时候我老板和我说ReadToEnd方法很好,应该用ReadToEnd,因为如果ReadToEnd方法还没ReadToLine性能高的话,那么就说明.NET框架有问题。

后来一直没解决,而这个程序又要Release了,怎么办呢,我偷偷的改成了ReadToLine方法,然后程序就OK了,虽然CPU会偶尔消耗的高一点,但是无伤大雅。

最近忽然无聊起来又想到这个问题,对任何问题也不能放过,而因为这个问题我也专门写了一个CodeTimer,程序性能计数器,可以到这里看到,顺便计算一下我们程序的性能。程序代码如下所示。

static void Main(string[] args)
{
    CodeTimer timer
= new CodeTimer(false);
    timer.Time(
"ReadToEnd", 100, ReadToEnd);
    timer.Time(
"ReadLine",100,ReadLine);
    Console.ReadKey();
}

static void ReadToEnd()
{
    StreamReader reader
= new StreamReader("test.txt");
    
string text = reader.ReadToEnd();
}

static void ReadLine()
{
    StreamReader reader
=new StreamReader("test.txt");
    
while (reader.Peek() > 0)
    {
        
string tempString = reader.ReadLine();
    }
}

上面的代码很简单,就是一个ReadToEnd方法和ReadToLine方法,然后用我们的性能计数器计算执行的过程,这里test.txt是一个大文件,大概有4-5M左右,然后我们用ReadToEnd和ReadToLine方法读文件各100遍,我们可以看看结果。

ReadToEnd
——————–
Time Elapsed:80,490ms
CPU Cycles:559,375,000
Gen 0: 699
Gen 1: 699
Gen 2: 700

ReadLine
——————–
Time Elapsed:47,066ms
CPU Cycles:468,437,500
Gen 0: 31249
Gen 1: 4
Gen 2: 701

我们可以看到ReadToEnd的方法消耗的时间很多,比ReadToLine方法要多一倍出来。但是ReadToLine方法的垃圾回收回收了近3万次,在性能上也几乎差不多,但是从内存的增长上面来说,ReadToEnd方法会造成内存泄漏,这是为什么呢。

这是因为首先string是一个独特的类型,在.NET中是非常特殊的一种数据结构,可以看做为引用类型,在处理变量的时候,.NET也会有一个“池,当我们下次再访问string变量的时候,看有没有这个变量,再进行处理,而所以在回收的时候,并不会完全的回收我们的string变量,特别是一次读4M的文件到内存中,一次回收大约会回收30%的空间,但是在循环体之内的话,回收的就更少了,因为作为告诉交换的变量,string的处理是特别复杂的。

所以在处理大文件的时候,我们最好不要用ReadToEnd方法,那么如果是小文件的话呢,我们可以看看结果。

ReadToEnd
——————–
Time Elapsed:21ms
CPU Cycles:312,500
Gen 0: 2
Gen 1: 0
Gen 2: 1

ReadLine
——————–
Time Elapsed:20ms
CPU Cycles:156,250
Gen 0: 2
Gen 1: 0
Gen 2: 2

我们可以看出来在读小文件的时候,大部分情况ReadToEnd和ReadToLine是没有很大的区别的,只是相差1毫秒左右,但是在结果上,ReadToLine还是少有胜算的(多执行几次)。所以说在读小文件的时候,ReadToEnd或者ReadToLine就没那么重要了。但是这里有一个问题就出现了。在读文件之后,我们必定是要处理一些文字的,比如用正则表达式匹配文字,是不是还是这样的结果呢?因为光比较ReadToLine和ReadToEnd方法在性能上并不会有很大的差异。OK,我们再把代码改一下(注意,这里都匹配最后一行的结果,保证前面全部遍历到了,而且只遍历10次)。

static void Main(string[] args)
{
    CodeTimer timer
= new CodeTimer(false);
    timer.Time(
"ReadToEnd", 10, ReadToEnd);
    timer.Time(
"ReadLine",10,ReadLine);
    Console.ReadKey();
}

static void ReadToEnd()
{
    StreamReader reader
= new StreamReader("test.txt");
    
string text = reader.ReadToEnd();
    
if (Regex.IsMatch(text, "TMID=%229fe92282539b467a8ad1bcc214e6991f
        %22%20MI=%22"))
    {
        
//do something
        string str = string.Empty;
    }
    text
= null;
}

static void ReadLine()
{
    StreamReader reader
=new StreamReader("test.txt");
    
while (reader.Peek() > 0)
    {
        
string tempString = reader.ReadLine();
        
if (Regex.IsMatch(tempString, "TMID=%229fe92282539b467a8ad1bcc214e6991f
            %22%20MI=%22"))
        {
            tempString
= null;
            
break;
        }
    }
}

我们可以来看看结果,结果如下。

ReadToEnd
——————–
Time Elapsed:23,782ms
CPU Cycles:65,625,000
Gen 0: 34
Gen 1: 34
Gen 2: 20

ReadLine
——————–
Time Elapsed:3,346ms
CPU Cycles:33,281,250
Gen 0: 1744
Gen 1: 0
Gen 2: 21

我们可以看到,在时间上,ReadToLine是完胜的,虽然做正则表达式的次数多了,但是在性能上和内存上的优势还是有很显著的效果的,现在我们再来读小文件,看看结果。

ReadToEnd
——————–
Time Elapsed:16ms
CPU Cycles:0
Gen 0: 0
Gen 1: 0
Gen 2: 1

ReadLine
——————–
Time Elapsed:4ms
CPU Cycles:0
Gen 0: 0
Gen 1: 0
Gen 2: 2

我们从结果可以看到,性能上的差别还是很大的,ReadToEnd是ReadToLine的好几倍。

结论

当然这里并不能说明ReadToLine和ReadToEnd哪个的性能更高,因为应用的场合并不相同,但是从运行的结果来说我们已经比较清楚的知道谁的性能高。在处理大型文件的时候,ReadToLine的方法虽然CPU占用会高,但是从时间和性能上来说是非常划得来的,而在处理小型文件的时候,ReadToLine和ReadToEnd方法本质上没有什么性能的优劣。但是当处理我们的字符的时候,ReadToLine又略胜一筹。

当然,不同的场合也有不同的性能,如果你关注逻辑而不关注性能的话,在少量的文件中,这两种方法都是没有很大的区别的。

300路过 2评论 DotNet 阅读全文..

最近在学习一些基础方面的东西,当然也在做一些很有意思的尝试,比如做Windows Mobile方面的开发以及校内或者Facebook方面的开发,但是学习过程中,程序员的能力不在于是否能做出应用,我相信70%的程序员都能够良好的制作出程序,20%的程序员都能够快速的制作出程序,而10%的程序员能够快速的制作出高质量的程序,而如果要成为10%之内的话,基础很重要,所以测试程序性能也很重要。

在C#中,没有很好的性能计数器,所以我就想动手做一个性能计数器,来计算我们的程序运行了多长时间。我曾经有一个同事用DataTime相减做计数用,虽然可以实现但是不太精确而且结果页不是很理想,所以这里就用CPU TIME来计数。所以我们写一个CodeTimer类。CodeTimer类有构造函数,代码如下所示。

private bool isVista;

/// <summary>
/// Init of the CodeTimer
/// </summary>

public CodeTimer(bool isVista)
{
    
this.isVista = isVista;

    Process.GetCurrentProcess().PriorityClass
= ProcessPriorityClass.High;
    Thread.CurrentThread.Priority
= ThreadPriority.Highest;
}

首先上面有一个私有的变量isVista,这个地方在构造函数中首先被初始化,具体为什么这样做,后面再讨论,这里首先将当前的进程优先级设高,以便我们的timer能够快速的响应,达到精确的效果。除了初始化之外,我们有一个核心方法Time,Time方法用来计算我们的方法执行的效率。示例代码如下。

/// <summary>
/// Main Method of the Time
/// </summary>

public void Time(string name, int iteration, Action action)
{
    
if (!String.IsNullOrEmpty(name))
    
{
        
//用GC去收集并记录收集的次数
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        
int[] gcCounts = new int[GC.MaxGeneration + 1];

        
for (int tempNum = 0; tempNum < GC.MaxGeneration; tempNum++)
        
{
            gcCounts[tempNum]
= GC.CollectionCount(tempNum);
        }


        
//start to watch the cup time,就是观察cpu time了
        Stopwatch watcher = new Stopwatch();
        watcher.Start();
        
ulong cycleCount = GetCycleTime();
        
for (int i = 0; i < iteration; i++) action();
        
ulong cpuCycles = GetCycleTime() - cycleCount;
        watcher.Stop();

        Console.WriteLine(
"Time Elapsed:" + watcher.ElapsedMilliseconds.ToString("N0")
                                  
+ "ms");
        Console.WriteLine(
"CPU Cycles:" + cpuCycles.ToString("N0"));

        
for (int i = 0; i <= GC.MaxGeneration; i++)
        
{
            
int count = GC.CollectionCount(i) - gcCounts[i];
            Console.WriteLine(
"Gen " + i + ": " + count);
        }

    }

    
else
    
{
        Console.WriteLine(
"Test Name cannot be empty");
    }

}


/// <summary>
/// Get the Thread time
/// </summary>

private ulong GetCycleTime()
{
    
ulong cycleCount = 0;

    
long l;
    
long kernelTime, userTimer;

    
//我们可以看到isVista在这里体现,因为如果不是Vista系统
    
//那么调用的WIN API也不同
    if (isVista)
    
{
        QueryThreadCycleTime(GetCurrentThread(),
ref cycleCount);
        
return cycleCount;
    }

    
else
    
{
        GetThreadTimes(GetCurrentThread(),
out l, out l, out kernelTime, out userTimer);
        
long time = kernelTime + userTimer;
        
//将long转换成ulong类型
        return (ulong)time;
    }

}

上面的代码我做了注释,所以就很容易看懂,也是根据老赵的思路来写,但是也是修改了一下老赵的类的设计,因为老赵并不支持Vista以下的系统。OK,言归正传,既然统计了方法,那么我们现在就添加一些API的调用,这里就很容易了,代码如下。

[DllImport("kernel32.dll")]
[
return: MarshalAs(UnmanagedType.Bool)]
private static extern bool QueryThreadCycleTime(IntPtr threadHandle, ref ulong cycleTime);

[DllImport("kernel32.dll")]
private static extern IntPtr GetCurrentThread();

[DllImport("kernel32.dll")]
private static extern bool GetThreadTimes(IntPtr hThread, out long lpCreationTime,
out long lpExitTime, out long lpKernelTime, out long lpUserTime);

这样我们一个性能计数器就成功了,在使用方法上,我们通过Time这个方法指导需要传递3个参数,一个参数是name,一个参数是循环次数,一个参数是方法体的名称,如果我们要循环调用某个方法3次,可以使用Time("调用这个方法",3,方法名称),其中嗲用这个方法只不过是一种描述罢了,方便记忆。使用方法上面,我们可以按照下面的方法来使用。

static void Main(string[] args)
{
    CodeTimer timer
= new CodeTimer(false);
    timer.Time(
"Test count", 1, Count);
    Console.ReadKey();
}


static void Count()
{
    
int result = 0;
    
for(int i=0;i<10;i++)
    
{
        System.Threading.Thread.Sleep(
32);
        result
+= i;
    }

}

运行效果如下所示。

代码下载,可删除Web项目。。因为传多了。。

Test.rar (60.00 kb)

226路过 0评论 DotNet 阅读全文..