第一篇:c#进销存实习总结
实习总结
一、实习目的:
掌握进销存的基本业务软件流程及C/S结构应用开发技术。通过本次学习,加深对软件行业的了解。掌握软件开发的流程,体系。把所学习的理论知识和实践项目联系起来。培养发现,分析,解决问题的能力。积累软件开发方面的经验。为自己将来就业做准备。
二、实习内容:
月xx日,正式开始了实习工作。准备做的项目是DotNet开发进销存管理软件。开始做需求分析。多层架构的搭建,然后逐步实现和完善雇员信息管理,商品信息管理,顾客信息及联系人的管理。商品采购,退货的管理。这些窗体的功能。如,信息的简单添加,删除,编辑等操作。基本信息的格式验证。窗体控件的应用属性设置,数据库的创建连接。后期窗体界面的美化,实习中遇到的错误及解决方法。
三、实习结果:
进销存软件的基本框架和一些功能的完成。遗憾的是软件的有些功能还未实现,如报表、退货等部分,一些细节的验证还有存在异常。
四、实习总结或体会:
为期八周的实习结束了,时间虽短,收获不少!
1、使我了解和熟悉了软件企业,了解一些基本商务礼仪,懂得了如何严谨踏实做事,如何诚实守信做人。
2、通过直接到企业基地实训过程,不仅进一步加深了对理论知识的理解。专业技能的掌握。更重要的是实践方面的一些宝贵经验。锻炼了自己的动手能力。
3、使我在一个的公司中,增强了团队协作能力,积攒了上下级沟通的方式技巧等有用的社交技能,增强了自信心和荣誉感。
4、遇到不懂的地方,自己先想方设法解决,实在不行可以虚心请教他人,而没有自学能力的人迟早要被企业和社会所淘汰。
五。结语:
最后感谢 安博冀软件实训基地为我提供宝贵的实习机会。老师的精心栽培。队友的帮助理解、我还是会继续我的学习和探索。
第二篇:C#实习总结
现在的社会日新月异,科技飞速发展,但是随之而来的环境问题也日益严重,当然这也有很多其他诸如人口、能源等因素,不过这些让人们越来越重视绿色产业,而软件就是首当其冲的一个。我积极参加了学校组织的软件实习,主要是对.Net方面的实训和实战,一方面想要继续深造之前在大学里面学习的软件方面的知识,另一方面,也是为了自己的职业规划,说直接点,就是以后找份好工作。虽然实训中时间安排有点紧,但很充实,学到了很多之前在大学课堂上没有学到的知识,可以说我从这次实训中的收获很大。
对于计算机编程,我可以说从高中就开始接触,大学从专科开始一直学习的都是软件开发的专业,学过C#、Java,学过开发,也学过测试。但是这次实训让我体会了和之前完全不同的学习感受,不仅是时间安排、或者说效率还是教学授课的方式上,都是和大学课堂完全不同的,不是一节课讲一点,也不是一点一点教给你,这个实训中更多的是培养我们独立动手解决实际问题的能力,分析考虑问题的方法思路,让我们真正成长起来。
学校为我们提供了良好的学习的平台,提供了好工作的面试机会。我们觉得我们更应该充分利用资源,珍惜机会,努力学习,不断地给自己“充电”,这样才能不断的开拓进取,勇于创新,才不至于被社会淘汰。
这次实习中,我们主要有S0、S1、S2三个阶段,S0阶段主要是自学C#基础知识,S1阶段是在老师的指导下学习C#基础编程、C#桌面应用开发、C#数据库应用开发、C#网络应用开发、软件测试和软件工程几个方面的技术方面的课程,S2阶段则是进入项目实战,做实际项目来进行巩固和总结。整个过程中,还有穿插了职业素养课和英语口语课,学技术的同时,我们也注重职业素养的培养和英语口语的锻炼。
到目前为止,我们实习的主要成果有(按时间顺序):Point24(24点游戏求解小程序)、HandsUp(举手游戏模拟小程序)、SimpleCalculator(简单计算器)、GottaU(捕捉鼠标小游戏)、TypeGame(打字小游戏)、WebBrowser(简易网络浏览器)、Checker(单机对战跳棋小游戏)、MDITextEditor(多文档界面文本编辑器)、FileManager(文件管理器)、SimpleMediaPlayer(简单媒体播放器)等,这些小程序或是项目,虽然和市面的那些软件比起来,在功能强大、界面美观、Bug稀少等方面都有欠缺,但是都是实打实的,我们自己动脑、动手做出来的,当然在做的过程中遇到了不少困难、挫折,但是正因为这些困难和挫折,我们才真正学到了知识和技术。更值得注意的则是,在老师带领我们做项目的过程中,我们从老师的言传身教中学到了很多很多编程思想、解决问题的方法思路等各种“无形”的知识。
众多收获之中,让我感触颇深的,则是老师给我们介绍的“10-90”原则和“Quality-Client-Cost”原则。“10-90”原则的意思是,我们通常都是花10%的代价就可以完成一件事或者一个项目的90%,而剩下的10%,却需要我们花费90%的金钱、精力等各种代价,能不能做到这最后的10%,往往是衡量一个人或者一家公司很重要的一个原则。而且这个原则还是可以嵌套的,最后10%的部分中,也可以再分成90%和10%,同样是开始的90%需要10%的代价,最后的10%需要90%的代价。这个原则不仅适用于做软件、做项目、做公司,其实人生中做人也是如此,很多时候最后的10%是很重要的一把标尺。虽然不能说这个原则放之四海而皆准,但是确实是适用于很多地方、很多方面,让我获益良多。“Quality-Client-Cost”原则讲的则是做软件或是做软件公司的一个原则,就是质量一定要放在第一位,客户在第二位。我自己原本心目中对这三项的排序是Client-Quality-Cost”,但是老师的讲解点醒了我,最具有说服力的例子,就是像微软这样的大公司,都是Quality第一的,他们宁可花费多一些,价格贵一些,但是一定把产品尽善尽美。这个原则同样可以用在人生中,Quality就是人品、品德、素质,Client是朋友、客户、亲人,我们当然应该把个人的品格放在第一位。这些都属于这次实习中,我在技术以外的收获。
另外在实习还有职业素养课中,也学到了很多其他在今后的工作中,要注意的东西,首先要有团队合作精神,现金的大中型软件的编写,分工越来越细,这样在开发、测试的过程中,团队的合作、成员间的交流就变得尤为重要,是决定效率的重要因素;要学会独立解决问题,工作不比在学校学习,遇到的各种实际问题不会有老师专门帮助解决,自己独立分析、解决问题的能力就变得极其重要;要有创新精神,在工作和学习中,如果只是一味的模仿、学习,那就永远不会真正得学得得心应手、融会贯通,必须自己去实践,在实践中创新,这样才能把学来的,变成自己的;要有耐心,学会自我规划和管控,耐心自是不必多说,有耐心才能把事情仔细做好,而公司的管理制度和学校根本上的不同,使得自我规划和管控就成了能否按时圆满完成任务的很重要的条件。
通过这次实习,我在个人素质方面有较大的提高,不仅是在C#的技术方面,还包括面对、分析和处理问题的思路、能力,思维的创造性和全面性,交流和沟通能力,英语口语水平,同时也克服了一些自己的缺点,获得很大进步。
总的来说,这次实习对我有很大意义,不仅巩固和实践了之前在大学课堂上学到的各种知识,扩充了自己对软件技术方面的知识储备,同时也给了我很大的成就感,增强了我的自信。并不是说因为我做成的很难的程序,或者做出来的软件很强很完善,但都是自己认真完成的,过程中有遇到各种困难和挫折,但是经
过网上查资料、小组讨论、向老师请教等几种途径,最终都克服了,当最终自己完成时,总会有一些成就感。
此外,我还人知到在以后的工作和学习中,不仅要努力学习和锻炼专业技能知识,包括C#技术、算法技巧、MVC模式框架的开发、中间技术等等,而且也要注意让自己兴趣广泛起来,拓宽自己的知识面,多积累各种知识,这不仅对以后的软件开发工作有一定好处,对自身的个人修养的提升也是大有裨益。
社会的竞争是激烈的,我想我们应该好好把握住大学学习的时间,充实、完善自我,掌握更多的专业知识,加强实践和设计能力,同时也注意全面发展,这样更有利于将来的发展,在自己的专业领域有所作为。
第三篇:C#总结
引用类型是类型安全的指针,它们的内存是分配在堆(保存指针地址)上的。String、数组、类、接口和委托都是引用类型。
强制类型转换与as类型转换的区别:当类型转换非法时,强制类型转换将抛出一System.InvalidCastException异常,而as不会抛出异常,它返回一个null值。用using创建别名:using console = System.Console;访问限定符:
public 该成员可以被其他任何类访问 protected 该成员只能被其派生类访问
private 该成员只能被本类的其他成员访问 internal 该成员只能在当前编译单元的其他成员访问 带参数列表和返回值的Main方法: class Test {
public static int Main(string[] args)
{
foreach(string arg in args)
{
...}
} } 构造函数(constructor)包括实例构造函数和静态构造函数。构造函数与类名相同,且不能有返回值。例:
class TestClass {
TestClass()//实例构造函数:可以访问静态成员和实例成员,用于初始化实例成员
{
...}
static TestClass()//静态构造函数:只能访问静态成员,用于初始化静态成员
{
...} } 类的静态成员属于类所有,不必生成实例就可以访问,它是在载入包含类的应用程序时创建的,但静态方法不能访问类的实例变量和方法。通常,静态变量是在定义时就赋初始值的。类的实例成员属于类的实例所有,不创建实例对象就无法对其进行访问,实例成员可以访问类的
静态成员和其它实例成员。调用基类的析构函数: class A {
public A()
{
...} } class B {
public B(): base()//调用基类的析构函数
{
...} } 常量:其值是在编译时设定的,必须是数值文字。默认状态下常量是静态的。例: class A {
public const double pi = 3.1415;} 常量是编译时就确定的值,只读字段是在运行才能确定的值。比如运行时才能确定的屏幕分辨率。
只读字段只能在类的析构函数中赋值。静态只读字段: class A {
public static readonly int ScreenWidth;//静态只读字段
static A()
//静态析构函数
{
ScreenWidth = 1024;//在静态析构函数中初始化
} } 在类的继承中,类的析构函数是不会被继承的。一个派生类只能从一个基类继承,不能同时从多个基类继承,但可以通过继承多个接口来达到相同目的。实现多继承的唯一方法就是使用接口。例:
class MyFancyGrid: Control, ISerializable, IDataBound {...} 密封类是不能继承的类,抽象类不能被定义为密封类,且密封类的私有成员不能用protected修饰,只能用private。例: sealed class A {...} 关键字ref和out用于指定用引用方式传递方法的参数。
它们的区别是:ref参数必须初始化,而out参数不需要初始化。所以在方法处理代码依赖参数的初始化值时使用ref,不依赖初始化值时使用out。对out参数即使在传递前对其进行了初始化,其值也不会传递到方法处理函数内部。传递时系统会将其设为未初始化。所以在方法内部必须对out参数进行初始化。
方法重载时,必须参数数目和参数类型其中之一不同,返回值不同不能作为重载。C#不支持方法的默认值,只能通过方法重载来实现。例: class A {
int Method(int a)
{
...}
void Method(int a, int b)//参数数目不同
{
//返回值不同不能作为重载
...} } params参数用于一个不定数目参数的方法,一般后面跟一个数组。例: class A {
public void Method(params int[] i)
{
...} } 方法的覆盖:指派生类覆盖基类的同名方法,有二种方法
1)第一种是在派生类要覆盖的方法前面加new修饰,而基类不需要作任何改动。这种方法的缺点是不能实现多态。例: class A {
public void Method()//无需任何修饰
{
...} } class B: A
//从基类继承
{
new public void Method()//覆盖基类的同名方法
{
...} } class TestClass {
A Instance = new B();
Instance.Method();//这时将调用类A的Method方法,而不是类B的Method方法 } 2)第二种是在派生类要覆盖的方法前面加override修饰,而基类的同名方法前面加virtual修饰。这样就能实现多态,例: class A {
virtual public void Method()
//基类定义虚方法
{
//虚拟方法不能定义为private,因为private成员对派生类是无法访问的...} }
class B: A
//从基类继承 {
override public void Method()
//派生类覆盖基类的同名虚方法
{
...} } class TestClass {
protected void Test()
{
A Instance = new B();
//定义一个实例,类型为基类,从派生类创建
//派生类总是能够向上转换为其基类
Instance.Method();
//将调用派生类B的Method方法,而不是基类的,这就是多态
} } 说明:new修饰的方法覆盖不能实现多态的原因,是因为使用new时编译器只会实现早期绑定(early binding)。即调用的方法在编译时就决定了:编译器看到Instance.Method()而Instance的类是A,就会调用类A的Method()方法。
override修饰的方法覆盖可以实现多态的原因,是因为实现了后期绑定(late binding)。使用override时强制编译器在运行时根据类的真正类型正确调用相应的方法,而不是在编译时。
而基类的同名方法必须加virtual修饰。
类的静态方法可能通过 类名.静态方法名 这种格式来调用,不能使用 实例名.静态方法名 这种方法调用。
因为类的静态方法为类所有(是属于类本身的),而非实例所有(不是属于类的实例的)。类的静态方法可以访问类的任何静态成员,但不能访问类的实例成员。C#中类的变量称为字段。类的public变量称为类的公共字段。
类的属性由一个protected(也可以是private)字段和getter和setter方法构成: class Address {
protected string zipCode;//protected字段,注意大小写
public string ZipCode
{
get
//getter方法
{
return zipCode;
}
set
//setter方法
{
zipCode = value;//被传递的值自动被在这个value变量中
}
};} 只读属性是指省略setter方法的属性,只读属性只能读取,不能设置。
属性也可以用限定符virtual,override和abstract修饰,功能同其他类的方法。
属性有一个用处称为懒惰的初始化(lazy initialization)。即在需要类成员时才对它们进行初始化。如果类中包含了很少被引用的成员,而这些成员的初始化又会花费大量的时候和系统资源的话,懒惰的初始化就很有用了。C#中数组对象共同的基类是System.Array。将数组声明为类的一个成员时,声明数组与实例化数组必须分开,这是因为只能在运行时创建了类的实例对象之后,才能实例化数组元素值。声明:
int[] intArray;//一维数组 int[,] int3Array;//三维数组 初始化:
intArray = new int[3] {1,2,3};int[,] int2Array = new int[2,3] {{1,2,3},{4,5,6}};//声明时可以初始化 遍历:
1)一维数组
for(int i = 0;i < intArray.Length;i++);//Array.Length返回数组所有元素的个数 foreach(int i in intArray);for(int i = 0;i < intArray.GetLength(0);i++);//Array.GetLength(0)返回数组第一维的个数 2)多维数组
for(int i = 0;i < int3Array.GetLength(0);i++)//遍历三维数组
for(int j = 0;j < int3Array.GetLength(1);j++)
for(int k = 0;k < int3Array.GetLength(2);k++)
{
...} 数组的维数就是该数组的秩(Rank)。Array.Rank可以返回数据的秩。锯齿数组(jagged Array)是元素为数组的数组,例:
int[][] jaggedArray = new int[2][];//包含二个元素,每个元素是个数组 jaggedArray[0] = new int[2];//每个元素必须初始化 jaggedArray[1] = new int[3];for(int i = 0;i < jaggedArray.Length;i++)//遍历锯齿数组
for(int j = 0;j < jaggedArray[i].Length;j++)
{
...} 类的属性称为智能字段,类的索引器称为智能数组。由于类本身作数组使用,所以用this作索引器的名称,索引器有索引参数值。例: using System;using System.Collections;class MyListBox {
protected ArrayList data = new ArrayList();
public object this[int idx] //this作索引器名称,idx是索引参数
{
get
{
if(idx >-1 && idx < data.Count)
{
return data[idx];
}
else
{
return null;
}
}
set
{
if(idx >-1 && idx < data.Count)
{
data[idx] = value;
}
else if(idx = data.Count)
{
data.Add(value);
}
else
{
//抛出一个异常
}
}
} } 接口是二段不同代码之间约定,通过约定实现彼此之间的相互访问。C#并不支持多继承,但通过接口可实现相同功能。当在接口中指定了实现这个接口的类时,我们就称这个类“实现了该接口”或“从接口继承”。一个接口基本上就是一个抽象类,这个抽象类中除了声明C#类的其他成员类型——例如属性、事件和索引器之外,只声明了纯虚拟方法。接口中可以包含方法、属性、索引器和事件——其中任何一种都不是在接口自身中来实现的。例:
interface IExampleInterface {
//property declaration
int testProperty { get;}
//event declaration
event testEvevnt Changed;
//mothed declaration
function void testMothed();
//indexer declaration
string this[int index] { get;set;} } 说明:定义接口时,在方法、属性、事件和索引器所有这些接口成员都不能用public之类的访问限定符,因为所有接口成员都是public类型的。因为接口定义了一个约定,任何实现一个接口的类都必须定义那个接口中每一个成员,否则将编译失败。例: using System;public class FancyControl {
protected string data;
public string Data
{
get {return this.data;}
set {data = value;}
} } interface IValidate {
bool Validate();//接口方法
} public class MyControl: FancyControl, IValidate {
public MyControl()
{
data = “my control data”;
}
public bool Validate()//实现接口
{
if(data == “my control data”)
return true;
else
return false;
} } class InterfaceApp {
MyControl myControl = new MyControl();
IValidate val =(IValidate)myControl;//可以将一个实现某接口的类,转换成该接口
bool success = val.Validate();//然后可调用该接口的方法 } 也可以用:bool success = myControl.Validate();这种方法来调用Validate方法,因为Validate在类MyControl中是被定义成public的,如果去除public,Validate方法被隐藏,就不能用这种方法调用了,这样隐藏接口方法称为名字隐藏(name hiding)。可以用:类实例 is 接口名 来判断某个类是否实现了某接口,例: myControl is IValidate //MyControl类的实例myControl是否实现了IValidate接口
当然,也可用as来作转换,根据转换结果是否为null来判断某个类是否实现了某接口,例: IValidate val = myControl as IValidate;if(null == val){...//没有实现IValidate接口 } else {...//实现了IValidate接口
}
如果一个类从多个接口继承,而这些接口中如果定义的同名的方法,则实现接口的方法时,必须加接口名来区别,写成 接口名.方法名。假设Test类从IDataStore和ISerializable二个接口继承,而这二个接口都有SaveData()方法,实现SaveData()方法时必须写成: class Test: ISerializable, IDataStore {
void ISerializable.SaveData()
{
...}
void IDataStore.SaveData()
{
...} } 如果一个类从多个接口继承,为了方便可以定义一个新的接口,这个接口继续多个接口,然后类直接从这个接口继承就可以了,这个叫合并接口。例: interface ISaveData: ISerializable, IDataStore { //不需要定义任何方法或成员,只是用作合并 } class Test: ISaveData //只要继承ISaveData就可以了 {...} C# 操作符优先级(从高到低)
初级操作符()x.y f(x)a[x] x++ x--new typeof sizeof checked unchecked 一元操作符 +位移操作符 << >> 关系操作符 < > <= >= is 等于操作符 == 逻辑与
& 逻辑异或 ^ 逻辑或
| 条件与
&& 条件或
|| 条件操作符 ?: 赋值操作符 = *= /= %= +=-= <<= >>= &= ^= |= 所有的二元操作符除赋值符外都是左联合的,即从左到右计算。
typeof()运算符可以从一个类名得到一个System.Type对象,而从System.Object对象继承来的GetType()方法则可从一个类实例来得到一个System.Type对象。例: Type t1 = typeof(Apple);//Apple是一个类名
Apple apple = new Apple();//apple是Apple类的一个实例 Type t2 = apple.GetType();//t1与t2是相同的 通过反射得到一个类的所有成员和方法: Type t = typeof(Apple);string className = t.ToString();//得到类名
MethodInfo[] methods = t.GetMethods();//得到所有方法 foreach(MethodInfo method in methods){ //用method.ToString()得到方法名 } MemberInfo[] members = t.GetMembers();//得到所有成员 foreach(MemberInfo member in members){ //用member.ToString()得到成员名 } sizeof()操作符用来计算值类型变量在内存中占用的字节数(Bytes),并且它只能在unsafe(非安全)
代码中使用。例:
static unsafe public void ShowSizes(){
int i, j;
j = sizeof(short);
j = sizeof(i);} 尽可能使用复合赋值操作符,它比不用复合赋值操作符的效率高。for语句的语法为:
for(initialization;Boolean-expression;step)
embedded-statement 在initialization和step部份还可以使用逗号操作符,例: for(int i = '0', j = 1;i <= 'xFF';i++, j++)for(int i = 1, j = 1;i < 1000;i += j, j = i!~ ++--true false 二元:+32)/ 9)* 5;
} } 代表的(delegate)目的与C++中的函数指针相同,代表不是在编译时被定义的,而是在运行时被定义的。
代表主要有二个用途:回调(Callback)和事件处理(event)回调通常用于异步处理和自定义处理。例: class DBManager {
static DBConnection[] activeConnections;
//声明回调函数
public void delegate EnumConnectionCallback(DBConnection connection);
public static void EnumConnections(EnumConnectionCallback callback)
{
foreach(DBConnection connection in activeConnections)
{
callback(connection);//执行回调函数
}
} } //调用
class DelegateApp {
public static void ActiveConncetionCallback(DBConnection connection)//处理函数
{
...}
public void main()
{
//创建指向具体处理函数的代表实例(新建一个代表,让它指向具体的处理函数)
DBManager.EmnuConnectionCallback myCallback = new DBManager.EmnuConnectionCallback(ActiveConncetionCallback);
DBManager.EnumConnections(myCallback);
} } //使用静态代表,上面的调用改为 class DelegateApp {
//创建一个指向处理函数的静态代表
public static DBManager.EmnuConnectionCallback myCallback
= new DBManager.EmnuConnectionCallback(ActiveConncetionCallback);
public static void ActiveConncetionCallback(DBConnection connection)
{...} public void main()
{
DBManager.EnumConnections(myCallback);
} } //在需要时才创建代表,上面的调用改为
class DelegateApp {
//将创建代表放在属性的getter方法中
public static DBManager.EmnuConnectionCallback myCallback
{
get
{
retun new DBManager.EmnuConnectionCallback(ActiveConncetionCallback);
}
}
public static void ActiveConncetionCallback(DBConnection connection)
{...} public void main()
{
DelegateApp app = new DelegateApp();//创建应用程序
DBManager.EnumConnections(myCallback);
} } 可以将多个代表整合成单个代表,例: class CompositeDelegateApp {
public static void LogEvent(Part part)
{
...}
public static void EmailPurchasingMgr(Part part)
{
...}
public static void Main()
{
//定义二个代表
InventoryManager.OutOfStockExceptionMethod LogEventCallback
= new InventoryManager.OutOfStockExceptionMethod(LogEvent);
InventoryManager.OutOfStockExceptionMethod EmailPurchasingMgrCallback
= new InventoryManager.OutOfStockExceptionMethod(EmailPurchasingMgr);
//整合为一个代表,注意后加的代表先执行(这里是先执行LogEventCallback)
InventoryManager.OutOfStockExceptionMethod onHandExceptionEventsCallback
= EmailPurchasingMgrCallback + LogEventCallback;
//调用代表
InventoryManager mgr = new InventoryManager();
mgr.ProcessInventory(onHandExceptionEventsCallback);
//InventoryManager类的ProcessInventory方法的原型为:
//public void ProcessInventory(OutOfStockExceptionMethod exception);
} } 可以根据需要将多个代表自由地组合成单个代表,例: class CompositeDelegateApp {
//代表指向的处理函数(三个代表三个函数)
public static void LogEvent(Part part)
{
...} public static void EmailPurchasingMgr(Part part){...}
public static void EmailStoreMgr(Part part)
{
...}
public static void Main()
{
//通过数组定义三个代表
InventoryManager.OutOfStockExceptionMethod[] exceptionMethods
= new InventoryManager.OutOfStockExceptionMethod[3];
exceptionMethods[0] = new InventoryManager.OutOfStockExceptionMethod(LogEvent);
exceptionMethods[1] = new InventoryManager.OutOfStockExceptionMethod(EmailPurchasingMgr);
exceptionMethods[2] = new InventoryManager.OutOfStockExceptionMethod(EmailStoreMgr);
int location = 1;
//再定义一个代表(用于组合成单代表)
InventoryManager.OutOfStockExceptionMethod compositeDelegate;
//根据需要组合
if(location = 2)
{
compositeDelegate = exceptionMethods[0] + exceptionMethods[1];
}
else
{
compositeDelegate = exceptionMethods[0] + exceptionMethods[2];
}
//调用代表
InventoryManager mgr = new InventoryManager();
mgr.ProcessInventory(compositeDelegate);
} } C#的事件遵循“发布——预订”的设计模式。在这种模式中,一个类公布能够出现的所有事件,然后任何的类都可以预订这些事件。一旦事件产生,运行环境就负责通知每个订户事件已经发生了。
当代表作为事件的处理结果时(或者说定义具有代表的事件),定义的代表必须指向二个参数的方法:一个参数是引发事件的对象(发布者),另一个是事件信息对象(这个对象必须从EventArgs类中派生)。例: using System;
class InventoryChangeEventArgs: EventArgs //事件信息对象,从EventArgs类派生 {...//假设定义二个public属性string Sku和int Change } class InventoryManager
//事件的发布者 {
//声明代表
public delegate void InventoryChangeEventHander(object source, InventoryChangeEventArgs e);
//发布事件,event关键字可将一个代表指向多个处理函数
public event InventoryChangeEventHandler onInventoryChangeHander;
public void UpdateInventory(string sku, int change)
{
if(change == 0)
return;
InventoryChangeEventArgs e = new InventoryChangeEventArgs(sku, change);
//触发事件
if(onInventoryChangeHandler!= null)//如果有预订者就触发
onInventoryChangeHandler(this, e);//执行代表指向的处理函数
} } class InventoryWatcher
//事件的预订者 {
public InventoryWatcher(InventoryManager mgr)//mgr参数用于联结发布者
{
this.inventoryManager = mgr;
//预订事件,用 += 调用多个处理函数
mgr.onInventroyChangeHandler += new InventoryManager.InventoryChangeEventHandler(onInventoryChange);
//事件处理函数
void onInventroyChange(object source, InventroyChangeEventArgs e)
{
...}
InventoryManager inventoryManager;
} } class EventsApp
//主程序 {
public static void Main()
{
InventoryManager inventoryManager = new InventoryManager();
InventoryWatcher inventoryWatcher = new InventoryWatcher(inventoryManager);
inventoryManager.UpdateInventory(“111 006 116”,-2);
inventoryManager.UpdateInventory(“111 006 116”, 5);
} } Microsoft Windows NT和IBM OS/2等操作系统都支持占先型多任务。在占先型多任务执行中,处理器负责
给每个线程分配一定量的运行时间——一个时间片(timeslice)。处理器接着在不同的线程之间进行切换,执行相应的处理。在单处理器的计算机上,并不能真正实现多个线程的同时运行,除非运行在多个处理器 的计算机上。操作系统调度的多线程只是根据分配给每个线程时间片进行切换执行,感觉上就像同时执行。
上下文切换(context switching)是线程运行的一部分,处理器使用一个硬件时间来判断一个指定线程的时间片何时结束。当这个硬件计时器给出中断信号时,处理器把当前运行的线程所用的所有寄存器(registers)数据存储到堆栈中。然后,处理器把堆栈里那些相同的寄存器信息存放到一种被称为“上下文结构”的数据结构中。当处理器要切换回原来执行的线程时,它反向执行这个过程,利用与该线程相关的上下文结构,在寄存器里重新恢复与这一线程相关的信息。这样的一个完整过程称为“上下文切换”。多线程允许应用程序把任务分割为多个线程,它们彼此之间可以独立地工作,最大限度地利用了处理器时间。using System;using System.Threading;class SimpleThreadApp {
public static void WorkerThreadMethod()//线程的执行体
{
...//执行一些操作
}
public static void Main()
{
//创建一个线程代表指向线程的执行体,ThreadStart是创建新线程必须用到的代表
ThreadStart worker = new ThreadStart(WorkerThreadMethod);
Thread t = new Thread(worker);//用线程代表创建线程
t.Start();
//执行线程
} } 可以通过两种方式来得到一个Thread对象:一种是通过创建一个新线程来得到,如上例;另一种在正在执行的线程调用静态的Thread.CurrentThread方法。
静态方法Thread.Sleep(int ms)可以让当前线程(它自动调用Thread.CurrentThread)暂停指定毫秒的时间。
如果使用Thread.Sleep(0)那么当前线程将一直处于等待中,直到另一个线程调用这个线程的实例方法Thread.Interrupt方法,等待才会结束。使用Thread.Suspend方法也能挂起线程,Thread.Suspend方法可以被当前线程或其他线程调用,而Thread.Sleep(0)只能由当前线程在执行体中调用。当线程用Thread.Suspend挂起时,必须用Thread.Resume方法恢复。不论Thread.Suspend方法调用了多少次,只要调用Thread.Resume方法一次就可以线程恢复执行。用Thread.Suspend方法并不会阻塞线程,调用立即返回。而Thread.Sleep(0)则会阻塞线程。所以确切地说Thread.Sleep(0)暂停线程,而不是挂起线程。
使用Thread.Abort方法可以终止正在执行的线程。当Thread.Abort方法被调用时,线程不会立即终止执行。运行环境将会等待,直到线程到达文档中所描述的“安全点”。如果要确保线程已经完全停止,可以使用Thread.Join方法。这是一个同步调用,同步调用意味着直到线程完全停止,调用才会返回。
Thread.Priority属性用于设置的线程的优先级。其值是Thread.ThreadPriority枚举值,可以设为Highest, AboveNormal,Normal, BelowNormal, Lowest。缺省值是Thread.ThreadPriority.Normal。
线程的同步是为了解决多个线程同时使用同一对象产生的一些问题。通过同步,可以指定代码的临界区(critical section),一次只有一个线程可以进入临界区。使用System.Monitor类(锁定与信号量)进行线程同步: using System;using System.Threading;public void SaveData(string text)//线程执行函数或线程执行函数调用的对象的方法 {
...//执行其他一些不需要同步的处理
Monitor.Enter(this);//获取对象的Monitor锁
...//执行需要同步的处理
Monitor.Exit(this);//释放对象的Monitor锁
...//执行其他一些不需要同步的处理
} 说明:当执行Monitor.Enter方法时。这个方法会试图获取对象上的Monitor锁,如果另一个线程已经拥有了这个锁,这个方法将会阻塞(block),直到这个锁被释放。
也可用C#的lock语句来获得和释放一个Monitor锁。上面同步写成:public void SaveData(string text)//线程执行函数或线程执行函数调用的对象的方法 {
...//执行其他一些不需要同步的处理
lock(this)//获取对象的Monitor锁,代码块执行完成后释放Monitor锁
{
...//执行需要同步的处理
}
...//执行其他一些不需要同步的处理 } 也可以使用System.Threading名称空间的Mutex类(互斥类)进行线程同步。与Monitor锁一样,一次只有一个线程能获得一个给定的互斥。但Mutex要慢得多,但它增加了灵活性。例:
using System;using System.Threading;class Database {
Mutex mutex = new Mutex(false);//创建一个互斥,但不立即获得它
//注意:创建互斥在需要同步的方法之外,实际上它只要创建一个实例
public void SaveData(string text)//需要同步的方法
{
mutex.WaitOne();//等待获得互斥
...//需要同步的处理
mntex.Close();//释放互斥
} } Mutex类重载了三个构造函数:
Mutex()
//创建并使创建类立即获得互斥
Mutex(bool initiallyOwned)
//创建时可指定是否要立即获得互斥 Mutex(bool initiallyOwned, string muterName)//还可以指定互斥的名称 Mutex.WaitOne方法也重载了三次: Mutex.WaitOne()
//一直等待
Mutex.WaitOne(TimeSpan time, bool exitContext)//等待TimeSpan指定的时间 Mutex.WaitOne(int milliseconds, bool exitContext)//等待指定的毫秒 线程的用法:
1)并发操作:比如一个程序监视多个COM口,当每个COM接到信息时执行一段处理时。2)复杂长时间操作:一个长时间的复杂操作可能会使界面停滞,停止用户响应,如果还允许用户停止它,或者显示进度条、显示操作执行进程信息时。
反射(Reflection)就是能够在运行时查找类型信息,这是因为.NET编译的可执行(PE)文件中包括MSIL和元数据(metadata)。
反射的中心是类System.Type。System.Type是一个抽象类,代表公用类型系统(Common Type System, CTS)中的一种类型。
using System;using System.Reflection;//反射命名空间,必须引用 public static void Main(string[] args){
int i = 6;
Type t = i.GetType();
//根据实例得到类型
t = Type.GetType(“System.Int32”);//根据类型的字符名称得到类型
} 通过Assembly类可以得到已经编译.NET Framework程序的中所有类型,例: using System;using System.Diagnostics;//为了使用Process类 using System.Reflection;//为了使用Assembly类 class GetTypesApp {
protected static string GetAssemblyName(string[] args)
{
string assemblyName;
if(0 == args.Length)//如果参数为空,取当前进程的名称
{
Process p = Process.GetCurrentProcess();
assemblyName = p.ProcessName + “.exe”;
}
else
assemblyName = args[0];//取第一个参数,即当前运行程序名
return assemblyName;
}
public static void Main(string[] args)
{
string assemblyName = GetAssemblyName(args);
Assembly a = Assembly.LoadFrom(assemblyName);//调用编译程序集
Type[] types = a.GetTypes();
//得到多个类型
foreach(Type t in types)
//遍历类型数组
{
...//取得t.FullName,t.BaseType.FullName等类型信息
}
} } 一个应用程序可以包括多个代码模块。若要将一个cs文件编译一个模块,只要执行下面的命令:
csc /target:module 要编译的模块.cs //csc是C Sharp Compiler(C#编译器)然后在应用程序中using编译的模块.cs中的NameSpace即可应用了。要反射应用程序中所有代码模块(Module),只要:
Assembly a = Assembly.LoadFrom(assemblyName);//应用程序的物理文件名 Module[] modules = a.GetModules();foreach(Module m in modules){...//显示m.Name等
} 后期绑定(latebinding),例:
string[] fileNames = Directory.GetFiles(Environment.CurrentDirectory, “*.dll”);foreach(string fileName in fileNames){
Assembly a = Assembly.LoadFrom(fileName);
Type[] types = a.GetTypes();
foreach(Type t in types)
{
if(t.IsSubclassOf(typeof(CommProtocol)))//判断是否有CommProtocol的派生类
{
object o = Activator.CreateInstance(t);//生成实例
MethodInfo mi = t.GetMethod(“DisplayName”);
mi.Invoke(o, null);
//调用方法
}
} } //带参数的例子
namespace Programming_CSharp {
using System;
using System.Reflection;
public class Tester
{
public static void Main()
{
Type t = Type.GetType(“System.Math”);
Object o = Activator.CreateInstance(t);
// 定义参数类型
Type[] paramTypes = new Type[1];
paramTypes[0]= Type.GetType(“System.Double”);
MethodInfo CosineInfo = t.GetMethod(“Cos”, paramTypes);
//设置参数数据
Object[] parameters = new Object[1];
parameters[0] = 45;
//执行方法
Object returnVal = CosineInfo.Invoke(o, parameters);
Console.WriteLine(“The cosine of a 45 degree angle {0}”, returnVal);
}
} } 动态生成代码和动态调用的完整例子: //动态生成代码的部分 using System;using System.Reflection;using System.Reflection.Emit;//动态生成代码必须引用 namespace ILGenServer {
public class CodeGenerator
{
public CodeGenerator()
{
currentDomain = AppDomain.CurrentDomain;//得到当前域
assemblyName = new AssemblyName();//从域创建一个程序集
assemblyName.Name = “TempAssembly”;
//得到一个动态编译生成器,AssemblyBuilerAccess.Run表示只在内存中运行,不能保存
assemblyBuilder = currentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilerAccess.Run);
//从编译生成器得到一个模块生成器
moduleBuilder = assemblyBuilder.DefineDynamicModule(“TempModule”);
//模块生成器得到类生成器
typeBuilder = moduleBuilder.DefineType(“TempClass”, TypeAttributes.Public);
//为类添加一个方法
methodBuilder = typeBuilder.DefineMethod(“HelloWord”, MethodAttributes.Public, null, null);
//为方法写入代码,生成代码必须使用到IL生成器
msil = methodBuilder.GetILGenerator();
msil.EmitWriteLine(“Hello World”);msil.Emit(OpCodes.Ret);//最后还需要编译(build)一下类 t = typeBuilder.CreateType();
}
AppDomain currentDomain;
AssemblyName assemblyName;
AssemblyBuilder assemblyBuilder;
ModuleBuilder moduleBuilder;
TypeBuilder typeBuilder;
MethodBuilder methodBuilder;
ILGenerator msil;
object o;
Type t;
public Type T
{
get
{
return this.t;
}
}
} } //动态调用的部分
using System;using System.Reflection;using ILGenServer;//引用动态生成代码的类 public class ILGenClientApp {
public static void Main({
CodeGenerator gen = new CodeGenerator();//创建动态生成类
Type t = gen.T;
if(null!= t)
{
object o = Activator.CreateInstance(t);
MethodInfo helloWorld = t.GetMethod(“HelloWorld”);//为调用方法创建一个MethodInfo
if(null!= helloWorld)
{
helloWorld.Invoke(o, null);//调用方法
}
}
} } 调用DLL using System;using System.Runtime.InteropServices;//为了使用DLLImport特性
class PInvokeApp {
[DllImport(“user32.dll”, CharSet=CharSet.Ansi)] //CharSet.Ansi指定Ansi版本的函数(MessageBoxA),CharSet.Unicode指定Unicode版本的函数(MessageBoxW)
static extern int MessageBox(int hWnd, string msg, string caption, int type);//声明DLL中的函数
//[DllImport(“user32.dll”, EntryPoint=“MessageBoxA”)] //用这种方法使用不同的函数名
//static extern int MsgBox(int hWnd, string msg, string caption, int type);
//[DllImport(“user32.dll”, CharSet=CharSet.Unicode)] //调用Unicode版的DLL函数
//static extern int MessageBox(int hWnd, [MarshalAs(UnmanagedType.LPWStr)]string msg,// [MarshalAs(UnmanagedType.LPWStr)]string caption, int type);//将LPWStr翻译为string型,缺省情况系统只将LPStr翻译成string
public static void Main()
{
MessageBox(0, “Hello, World!”, “CaptionString”, 0);//调用DLL中的函数
} } 例2,使用回调: class CallbackApp {
[DllImport(“user32.dll”)]
static extern int GetWindowText(int hWnd, StringBuilder text, int count);
delegate bool CallbackDef(int hWnd, int lParam);
[DllImport(“user32.dll”)]
static extern int EnumWindows(CallbackDef callback, int lParam);
static bool PrintWindow(int hWnd, int lParam)
{
StringBuilder text = new StringBuilder(255);
GetWindowText(hWnd, text, 255);
Console.WriteLine(“Window Caption: {0}”, text);
return true;
}
static void Main()
{
CallbackDef callback = new CallbackDef(PrintWindow);
EnumWindows(callback, 0);
} } 关键字unsafe指定标记块在非控环境中运行。该关键字可以用于所有的方法,包括构造函数和属性,甚至还有方法中的代码块。关键字fixed负责受控对象的固定(pinning)。Pinning是一种动作,向垃圾收集(Garbage Collector, GC)指定一些不能被移动的对象。为了不在内存中产生碎片,.NET运行环境把对象四处移动,以便于最有效地利用内存。使用fixed后指定对象将不会被移动,所以就可以用指针来访问它。
C#中只能得到值类型、数组和字符串的指针。在数组的情况下,第一个元素必须是值类型,因为C#实际上是返回一个指向数组第一个元素的指针,而不是返回数组自身。& 取一个变量的内存地址(即指向该变量的指针)* 取指针所指变量的值-> 取成员
例:using System;class UnsafeApp {
public static unsafe void GetValues(int* x, int* y)
{
*x = 6;
*y = 42;
}
public static unsafe void Main()
{
int a = 1;
int b = 2;
GetValues(&a, &b);
} } fixed语法为:fixed(type* ptr = expression)statements其中type也可以为非控类型,也可是void;expression是任何产生一个type指针的表达式;statements是应用的代码块。例: fixed(int* f = &foo.x)//foo是Foo类的一个实例,x是Foo类的一个int属性 {
SetFooValue(f);//SetFooValue方法的定义为unsafe static void SetFooValue(int* x)} 传统的COM组件可以通过互操作层(COM Interop)与.NET运行环境交互。互操作层处理在托管运行环境和非托管区域中的COM组件操作之间传递所有的消息。
要使COM组件能在.NET环境中使用,必须为COM组件生成元数据。.NET运行环境用元数据层业判断类型信息。在运行时刻使用类型信息,以便生成RCW(Runtime Callable Wrapper,运行时可调用包装)。当.NET应用程序与COM对象交互时,RCW处理对COM对象的装载和调用。RCW还完成许多其他的工作,如管理对象标识、对象生存周期以及接口缓冲区。对象生存周期管理十分关键,因为.NET GC把对象到处移动,并且当对象不再使用时,自动处理这些对象。RCW服务告诉.NET,应用程序正与托管.NET组件交互,同时又使非托管COM组件“觉得”COM对象是被传统的COM客户端调用的。
为了为COM组件生成元数据包装,必须使用tlbimp.exe(TypeLib Importer)工具: tlbimp some_COM.tlb /out:som_COM.dll
第四篇:进销存的总结
进销存总结
据我的理解,进销存无非就是针对生产型企业而开发的一套管理系统,这样就更方便于企业的管理人员方便管理,提高了工作效率,从长远打算,也一定程度降低了人员成本,因此,对于生产型企业有一套自己的管理软件是非常必要的。
所谓进销存的“进”就是对于生产型企业准备生产的前期原材料准备,包括来料加工,具体的分为原材料的信息收集到各供应商的价格质量对比,再到对原材料的购买决策,既而进入仓库。
对于进销存的“销”就主要是客户下单,收款,发货,追回款,及客户关系管理。
而进销存的“存”就直白的说是仓库,包括原材料的入库和出库,及成品的出入库,当然也包含了废品的退货及盘点。
进销存针对各个行业都不一样,而大概的原理却差不多,他的意义主要在于能帮助企业方便管理,为企业减轻负担,诚如是,把这种不刊之论的思想深入企业决策人的心里,就不愁他不束手就擒。
第五篇:C#期末总结
C#期末总结
接触C#已经有半学期时间,对C#感触颇多。C#作为逻辑性极强,功能强大的程序编程语言,仅仅学习半学期是远远不够的,希望自己能在这条路上不断前行,能够活到老学到老。
跟绝大部分的初学者一样,当看见密密麻麻的代码,就觉得这会是一门枯燥无味的课程。但在平时生活中使用各种功能强大的app,应用程序等时,我又会对程序背后的代码感到好奇。就是在这样矛盾的心情中,我一点点初步掌握了C#。
C#中最难理解也是最有意思的莫过于类的使用了。总的来说类是属性相同的方法或对象的集合。编程语言中的类较为抽象,难以理解。编程语言中类的思想在现实生活有许多体现。比如我们通常会把马路上所有的四轮交通工具归为一类----汽车,在生物学中也使用界、门、纲、目、科、属、种把所有生物分级分类。似乎这样想,类很好理解。但在具体编程中会遇到许多问题,比如类成员中各个成员变量的访问权限问题以及类与类之间的逻辑关系问题,等等。解决这些问题,需要对事物和目标有准确的理解和把握,当然扎实的C#功底基础也是必不可缺的。如果能够充分使用类,那么代码的编写会变得井井有条,至少代码会看上去富有生命力。
编程的过程自然枯燥无味,但当看见自己编写的程序运行成功,那种满足感不言而喻。例如本学期的贪吃蛇游戏编程过程,就让我感触颇深。(首先必须承认的一点就是贪吃蛇游戏的主体代码是我借鉴网上大神,光是看懂捋清大神的代码就花了我一星期的时间,接下来就是添加完善代码)首先想要在游戏开始之前添加一个难度选择功能界面,这就必须解决两个问题:一个是怎样控制两个界面(难度选择界面和游戏界面)出现先后次序问题,另一个是如何保存已经选好的难度选择并在游戏中体现出来。对于第一个问题我的解决方案如下:先在speed.cs(难度选择代码区)文件中加入”this.Close();”语句;再在program.cs(程序的主入口)文件中加入”Application.Run(new Speed());”和”Application.Run(new form());”语句。加入”this.Close();”语句的目的是当难度选择完成后让难度选择界面自行关闭;加入”Application.Run(new Speed());”和”Application.Run(new form())”的目的是控制两个界面先后出现的次序。这个问题的解决过程还算顺利,它主要涉及函数调用。第二个问题的解决就比较难,大概耗费我两星期的时间。第二个问题属于在类与类之间调用变量问题。这一类问题在各种编程中会常常遇到。通过查各种资料和不断试验,我的解决方案如下:先添加一个新类PublicClass,然后在PublicClass定义一个静态变量z,再在主代码区调用z。添加新类的目的是使代码简洁明了并方便后期修改,定义变量z的目的是将难度选择值赋给z。这两个问题解决使得贪吃蛇程序能够顺利运行。但顺利运行并不代表这是一个合格的程序,同学反映说这个程序看上去并不是很美观,我意识到还需要在用户体验方面下工夫,没想到这给我带来不小的麻烦。我的计划是用图片代替方块来显示蛇身,但我错误以为只要将定义方块的函数修改成绘画图片的函数就可以了,导致的结果就是程序运行崩溃。我不断翻看所有的代码,一遍又一遍试验,但都找不到解决的办法。就这样过了三四天的时间,我甚至有了放弃的打算。但或许是印证了那句话“山重水复疑无路,柳暗花明又一村”,主代码中有段注释引起了我的注意------”//画出方块并显示在画板上”,这时我才恍然大悟:原来不管是方块还是图片都要定义并使用draw()才能在界面上显示出来,定义方块的函数只不过定义了最基本的方块属性(形状,颜色,大小等)。如果需要更换蛇身图片,只需要在”draw()”中更改就可以了(因为图片是从网上下载,图片的各个属性已固定无需再定义)。最终成功的给蛇换上了一件”新衣服”,程序的用户体验性大大提升。
完成贪吃蛇的编程后,我深深体会到C#是一款面向对象的编程语言。把解决问题的各个因素分门别类,实现分工合作以达到高效率的解决问题。就像现实中的社会分工一样,每个人都在各自的岗位上辛勤劳动,无论工作大小,国家社会离不开每个人的奉献付出。C#中还有其他有趣的控件,比如timer,picturebox,textbox等。灵活组合这些控件,能够编写出很多有意思的小程序。曾经看见网上有C#计算器,C#计时器的教程,觉得很有意思并尝试了一下,但发现很难实现程序,究其原因是因为作者的思维与我的思维有着很大的不同,这说明每个人解决问题的方式不同,而程序便是程序员思维的体现。每个自己编写的程序都是与自己的思维相通的,这么理解的话可以把程序看成自己思维在其他事物上的拓展,其乐无穷,乐此不疲。或许这就是为什么世界上有那么多的天才编程员愿意夜以继日的编写程序,或许这也就是为什么好莱坞每每拍出人工智能统治世界的科幻片总能席卷全球票房。名侦探柯南剧场版《贝克街的亡灵》中的超级人工智能诺亚方舟在天才计算机少年泽田弘树死后代替其意志继续在日本存活下去。想想确实可怕,人工智能究竟会改变人多少。计算机注定改变每个人的生活。因此掌握计算机无疑就是掌握了未来。
由于半学期的学习时间实在太少,想要深刻理解并灵活运用C#是需要多年的实践学习的。光是上课时间认真听讲是远远不够的,还需要课下不断的练习钻研。学习c#语言不能停留在学习它的语法规则,而是利用学到的知识编写c#语言程序,解决实际问题。即把c#语言作为工具,描述解决实际问题的步骤,由计算机帮助我们解题。只有通过上机才能检验自己是否掌握c#语言、自己编写的程序是否能够正确地解题。语法是学习编程语言的关键,光靠背语法,是远远不够的(其他语言学习也是如此)。记笔记是上课时候必须要做的一件事,但不能认为做了笔记就不用上课认真听讲了。C#作为一名理科学科是需要不断深入理解实践才能掌握的。有几点学习心得想分享一下:.不要妄自菲薄。不能因为遇到困难就不相信自己,每个人都是从零开始。2.坚持不懈。学习C#是件漫长的过程,需要不断激励自己鼓励自己。
3.多和他人交流。一个人的力量是渺小的,多个人相互交流不断切磋才能不断进步。4.不要偷懒,经常思考。C#中有许多思想是和现实生活相通的,不断对生活感悟对生活进行反思,从生活中寻找灵感,这样能提高对C#的兴趣以及学习的主动性和趣味性。
5.经常上机实验,动手操作。实践出真知,在实践中理解那些深奥晦涩难懂的专业知识。6.在编程时除了要认真谨慎之外,更重要的是掌握基本的知识,比如if语句和while语句的区别。初学者常常会被这两个语句搞得晕头转向,在编程过程也不会太在意两个语句的使用范围。再比如C#中命名空间,这是常常忽略的方面,初学者一开始编程时会发现定义一个变量或者函数常常会遇到系统提醒错误,开始认真检查下来,却怎么也找不到错在哪,这很有可能就是命名空间错误。
大学之前从未接触编程语言,对于C#的学习也是在一时兴起和怀疑自己中不断转换。平时会去图书馆借些C#书籍,但总有种意犹未尽的感觉。这种感觉或许来自对编程语言的兴趣,希望自己能学到更多关于编程语言的知识。