一:Ref和Out 的区别:
1、使用ref型参数时,传入的参数必须先被初始化。对out而言,必须在方法中对其完成初始化。
2、使用ref和out时,在方法的参数和执行方法时,都要加Ref或Out关键字。以满足匹配。
3、out适合用在需要retrun多个返回值的地方,而ref则用在需要被调用的方法修改调用者的引用的时候。
4、ref是有进有出,而out是只出不进。
二:装箱和拆箱
在应用中最大的一个意义就在于:理解数据从栈移动到堆的过程中所发生的性能消耗问题,反之亦然。
考虑一下以下的代码片段,当我们将一个值类型转换为引用类型,数据将会从栈移动到堆中。相反,当我们将一个引用类型转换为值类型时,数据也会从堆移动到栈中。
不管是在从栈移动到堆还是从堆中移动到栈上都会不可避免地对系统性能产生一些影响。
于是,两个新名词横空出世:当数据从值类型转换为引用类型的过程被称为“装箱”,而从引用类型转换为值类型的过程则被成为“拆箱”。
如果你编译一下上面这段代码并且在ILDASM(一个IL的反编译工具)中对其进行查看,你会发现在IL代码中,装箱和拆箱是什么样子的。下图则展示了示例代码被编译后所产生的IL代码。
装箱和拆箱的性能问题
为了弄明白到底装箱和拆箱会带来怎样的性能影响,我们分别循环运行10000次下图所示的两个函数方法。其中第一个方法中有装箱操作,另一个则没有。我们使用一个Stopwatch对象来监视时间的消耗。
具有装箱操作的方法花费了3542毫秒来执行完成,而没有装箱操作的方法只花费了2477毫秒,整整相差了1秒多。而且,这个值也会因为循环次 数的增加而增加。也就是说,我们要尽量避免装箱和拆箱操作。在一个项目中,如果你需要装箱和装箱,请仔细考虑它是否是绝对必不可少的操作,如果不是,那么 尽量不用。
虽然以上代码段没有展示拆箱操作,但其效果同样适用于拆箱。你可以通过写代码来实现拆箱,并且通过Stopwatch来测试其时间消耗。
三:值类型和引用类型
值类型将数据和内存都保存在同一位置,而一个引用类型则会有一个指向实际内存区域的指针。
通过下图,我们可以看到一个名为i的整形数据类型,它的值被赋值到另一个名为j的整形数据类型。他们的值都被存储到了栈上。
当我们将一个int类型的值赋值到另一个int类型的值时,它实际上是创建了一个完全不同的副本。换句话说,如果你改变了其中某一个的值,另一个不会发生改变。于是,这些种类的数据类型被称为“值类型”。
当我们创建一个对象并且将此对象赋值给另外一个对象时,他们彼此都指向了如下图代码段所示的内存中同一块区域。因此,当我们将obj赋值给 obj1时,他们都指向了堆中的同一块区域。换句话说,如果此时我们改变了其中任何一个,另一个都会受到影响,这也说明了他们为何被称为“引用类型”。
在.NET中,变量是存储到栈还是堆中完全取决于其所属的数据类型。比如:‘String’或‘Object’属于引用类型,而其他.NET基元数据类型则会被分配到栈上。下图则详细地展示了在.NET预置类型中,哪些是值类型,哪些又是引用类型。
四:堆和栈
堆(Stack) 栈(Heap)
堆和栈是.Net中内存分配的两种方式。
为了理解栈和堆,让我们通过以下的代码来了解背后到底发生了什么。
public void Method1()
{
// Line 1
int i=4;
// Line 2
int y=2;
//Line 3
class1 cls1 = new class1();
}
代码只有三行,现在我们可以一行一行地来了解到底内部是怎么来执行的。
Line 1:当这一行被执行后,编译器会在栈上分配一小块内存。栈会在负责跟踪你的应用程序中是否有运行内存需要 Line 2:现在将会执行第二步。正如栈的名字一样,它会将此处的一小块内存分配叠加在刚刚第一步的内存分配的顶部。你可以认为栈就是一个一个叠加起来的房间或盒子。在栈中,数据的分配和解除都会通过LIFO (Last In First Out)即先进后出的逻辑规则进行。换句话说,也就是最先进入栈中的数据项有可能最后才会出栈。 Line 3:在第三行中,我们创建了一个对象。当这一行被执行后,.NET会在栈中创建一个指针,而实际的对象将会存储到一个叫做“堆”的内存区域中。“堆”不会监测运行内存,它只是能够被随时访问到的一堆对象而已。不同于栈,堆用于动态内存的分配。 这
里需要注意的另一个重要的点是对象的引用指针是分配在栈上的。 例如:声明语句 Class1 cls1;
其实并没有为Class1的实例分配内存,它只是在栈上为变量cls1创建了一个引用指针(并且将其默认职位null)。只有当其遇到new关键字时,它
才会在堆上为对象分配内存。 离开这个Method1方法时(the fun):现在执行控制语句开始离开方法体,这时所有在栈上为变量所分配的内存空间都会被清除。换句话说,在上面的示例中所有与int类型相关的变量将会按照“LIFO”后进先出的方式从栈中一个一个地出栈。 需要注意的是:这时它并不会释放堆中的内存块,堆中的内存块将会由垃圾回收器稍候进行清理。
现在我们许多的开发者朋友一定很好奇为什么会有两种不同类型的存储?我们为什么不能将所有的内存块分配只到一种类型的存储上?
如果你观察足够仔细,基元数据类型并不复杂,他们仅仅保存像 ‘int i =
0’这样的值。对象数据类型就复杂了,他们引用其他对象或其他基元数据类型。换句话说,他们保存其他多个值的引用并且这些值必须一一地存储在内存中。对象
类型需要的是动态内存而基元类型需要静态内存。如果需求是动态内存的话,那么它将会在堆上为其分配内存,相反,则会在栈上为其分配。
五: 获取程序当前路径
有很多方法能够获取程序当前路径
- 获取当前 Thread 的当前应用程序域的基目录,它由程序集冲突解决程序用来探测程序集。(推荐使用这种方式)
string str = System.AppDomain.CurrentDomain.BaseDirectory;
返回结果: X:\xxx\xxx\ (.exe文件所在的目录+”\”)
- 获取新的 Process 组件并将其与当前活动的进程关联的主模块的完整路径,包含文件名(进程名)。
string str = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
返回结果: X:\xxx\xxx\xxx.exe (.exe文件所在的目录+.exe文件名)
- 获取和设置当前目录(即该进程从中启动的目录)的完全限定路径。
string str = System.Environment.CurrentDirectory;
返回结果: X:\xxx\xxx (.exe文件所在的目录)
string str = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
返回结果: X:\xxx\xxx\ (.exe文件所在的目录+”\”)
string str = System.IO.Directory.GetCurrentDirectory();
返回结果: X:\xxx\xxx (.exe文件所在的目录)
- 获取启动了应用程序的可执行文件的路径,不包括可执行文件的名称。(仅限WinForm程序)
string str = System.Windows.Forms.Application.StartupPath;
返回结果: X:\xxx\xxx (.exe文件所在的目录)
- 获取启动了应用程序的可执行文件的路径,包括可执行文件的名称。(仅限WinForm程序)
string str = System.Windows.Forms.Application.ExecutablePath;
返回结果: X:\xxx\xxx\xxx.exe (.exe文件所在的目录+.exe文件名)
public static string sApplicationPath = Assembly.GetExecutingAssembly ( ).Location;
返回结果: X:\xxx\xxx\xxx.dll (.dll文件所在的目录+.dll文件名)
string str = this.GetType ( ).Assembly.Location;
返回结果: X:\xxx\xxx\xxx.exe (.exe文件所在的目录+.exe文件名)
六:读写程序配置文件
程序配置文件作为程序重要配置信息文件,在项目开发过程中,我们可能需要对其进行读写操作。
配置文件在不同类型的项目中,名称不同:
- Web项目配置文件为:web.config
- WinForm项目配置文件为:app.config
.Net Framework为我们提供了相应类进行程序配置文件操作。
在配置文件中,有几个节点是可以进行自定的:
- appSettings:程序设置,key-value键值对信息
- connectionStrings:连接字符串
appSettins读写操作
配置文件内容格式:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DemoKey" value="*" />
</appSettings>
</configuration>
读:
String str = ConfigurationManager.AppSettings["DemoKey"];
写:
Configuration cfa = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
cfa.AppSettings.Settings.Add("key", "Name")||cfa.AppSettings.Settings["key"].Value = "name";
cfa.Save();
注意:调用ConfiguionManager必须要先在工程里添加system.configuration.dll程序集的引用。
获取ConnectionStrings配置节中的值
//// <summary>
/// 获取ConnectionStrings配置节中的值
/// </summary>
/// <returns></returns>
public static string GetConnectionStringsElementValue()
{
ConnectionStringSettings settings = System.Configuration.ConfigurationManager.ConnectionStrings["connectionString"];
return settings.ConnectionString;
}
保存值
//// <summary>
/// 保存节点中ConnectionStrings的子节点配置项的值
/// </summary>
/// <param name="elementValue"></param>
public static void ConnectionStringsSave(string ConnectionStringsName, string elementValue) { System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); config.ConnectionStrings.ConnectionStrings["connectionString"].ConnectionString = elementValue; config.Save(ConfigurationSaveMode.Modified); ConfigurationManager.RefreshSection("connectionStrings"); }
八:常用的Stream和Byte[]操作
Stream和Byte[]是C#中比较底层的操作,直接针对文件内容进行操作,所以掌握常用的操作,能增强我们开发技能。
将 Stream 转成 byte[]
public byte[] StreamToBytes(Stream stream)
{
byte[] bytes = new byte[stream.Length];
stream.Read(bytes, 0, bytes.Length);
// 设置当前流的位置为流的开始
stream.Seek(0, SeekOrigin.Begin);
return bytes;
}
将 byte[] 转成 Stream
public Stream BytesToStream(byte[] bytes)
{
Stream stream = new MemoryStream(bytes);
return stream;
}
将string转换为byte[]
System.Text.UnicodeEncoding converter = new System.Text.UnicodeEncoding();
byte[] inputBytes =converter.GetBytes(inputString);
string inputString = converter.GetString(inputBytes);
将byte[]转换为string
string inputString = System.Convert.ToBase64String(inputBytes);
byte[] inputBytes = System.Convert.FromBase64String(inputString);
FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
将 Stream 写入文件
public void StreamToFile(Stream stream,string fileName)
{
// 把 Stream 转换成 byte[]
byte[] bytes = new byte[stream.Length];
stream.Read(bytes, 0, bytes.Length);
// 设置当前流的位置为流的开始
stream.Seek(0, SeekOrigin.Begin);
// 把 byte[] 写入文件
FileStream fs = new FileStream(fileName, FileMode.Create);
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(bytes);
bw.Close();
fs.Close();
}
从文件读取 Stream
public Stream FileToStream(string fileName)
{
// 打开文件
FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
// 读取文件的 byte[]
byte[] bytes = new byte[fileStream.Length];
fileStream.Read(bytes, 0, bytes.Length);
fileStream.Close();
// 把 byte[] 转换成 Stream
Stream stream = new MemoryStream(bytes);
return stream;
}
二进制转换成图片
MemoryStream ms = new MemoryStream(bytes);
ms.Position = 0;
Image img = Image.FromStream(ms);
ms.Close();
this.pictureBox1.Image
图片转二进制
Bitmap BitReturn = new Bitmap();
byte[] bReturn = null;
MemoryStream ms = new MemoryStream();
BitReturn.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
bReturn = ms.GetBuffer();
|
请发表评论