如何优化结构体和类的大小?

类和结构体的区别,大家应该都知道,但是在开发过程中到底是用类还是结构呢?

要想知道这个问题的答案首先应该知道怎样估算对象和结构体的大小。

一、如何估算结构体的大小

结构是值类型,它的结构体的实例是存放在栈中或者堆中。结构体在内存中所占的大小,就是其字段所占的大小,但是,它的大小并不是所有字段大小相加,而是存在一个对齐的规则,在默认的对齐规则中,基本类型字段是按照自身大小对齐的,如byte是按1字节对齐。

struct A
{
byte a1;
}

如上面这个结构体的大小就是1字节,如果是下面这个:

struct A
{
byte a1;
int a2;
}

这个结构体所占内存大小是8字节,因为int是4字节对齐的,所以只能从第四个字节开始。

如果再添加一个字段:

struct A
{
byte a1;
int a2;
byte a3;
}

这个结构体大小是12,由于struct本身也要是对齐的,所以它的对齐规则是按照其中元素最大的对齐规则决定的。也就是说上面这个结构体要按照4字节对齐,不足4字节要补齐,所以是12个字节大小。

如果想要优化它的大小,可以调整顺序如下:

struct A
{
byte a1;
byte a3;
int a2;
}

这个时候这个结构体所占的大小就是8字节了。

二、如何估算类的大小

类是引用类型,它的对象实例存放在堆中,对象实例一定是会占用堆内存的,而在栈中,保存的是实例的引用。对象在堆中分成3个区域,vtable、monitor和字段。其中vtable是类的共有数据,包含静态变量和方法表,这个应该就是类本身所占用的大小和具体的对象无关。monitor是线程同步用的,这2个指针分别占用一个inptr.Size大小,字段是从第9个字节或17个字节开始的,字段的对齐规则和结构体的对齐规则相同,区别是Mono中对象的实例会把引用类型的引用放在最前面。一个对象实例的大小就是 inptr.Size *2+字段的大小。

通过调整字段的顺序,也可以优化对象的大小。

还可以通过StructLayoutAttribute自定义类和结构体的对齐方式。

[StructLayout(LayoutKind, Sequential, Pack = 1)]
public struct A
{
byte a1;
int a2;
byte a3;
}

上面这个结构体强制按照1字节对齐,所以他的大小是6字节。但是这样做可能会降低性能。

所以具体情况还是要具体分析,懂得了如何估算结构体和类的大小,就更容易知道该如何使用它们了。

微信网页登录器

   

   微信登录协议分析内容很复杂,所以就简单贴出一下分析的地址吧,不一一介绍了,直接上软件吧…… 哈哈

QQ截图20160627145117.png

获取uuid
 
2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=zh_CN&_
=1465359723508

获取二维码
https://login.weixin.qq.com/qrcode/odEPIEYLqg==

等待扫描
 
XA==&tip=0&r=-776229298&_=1465360049579

等待确认 - 返回ticket
 
SALw==&tip=0&r=-778037154&_=1465361861142

返回pass_ticket,wxuin,wxsid,crypt
 
dO_aBwmczuA@qrticket_0&uuid=YccSzzSALw==&lang=zh_CN&scan=1465361886&fun=new&ve
rsion=v2&lang=zh_CN

//初始化
https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-2015985609

接受消息
 
%40crypt_88ee86d2_88588b32587d869bb533dfb8aa61d360&sid=eAEXFuhHNB4EIfOs&uin=27
22200005&deviceid=e631912890957112&synckey=1_650814494%7C2_650814654%7C3_65081
4621%7C11_650813066%7C13_650800143%7C201_1465361892%7C1000_1465347961%7C1001_1
465347992%7C1002_1464837848%7C1005_1464769994&_=1465361861155

获取联系人列表
 
999&seq=0&skey=@crypt_88ee86d2_88588b32587d869bb533dfb8aa61d360

上传图片
https://file.wx2.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json

发送图片
 
h_CN

   登录器下载地址: http://pan.baidu.com/s/1nuBSPUL

c#异步委托的使用

创建线程的一种简单方式是定义一个委托,并异步调用它。 委托是方法的类型安全的引用。Delegate类 还支持异步地调用方法。在后台,Delegate类会创建一个执行任务的线程。

接下来定义一个方法,使用委托异步调用(开启一个线程去执行这个方法)


static int TakesAWhile(int data,int ms){

Console.WriteLine("TakesAWhile started!");

Thread.Sleep(ms);//程序运行到这里的时候会暂停ms毫秒,然后继续运行下一语句

Console.WriteLine("TakesAWhile completed");

return ++data;

}

public delegate int TakesAWhileDelegate(int data,int ms);// 声明委托

static void Main(){

TakesAWhileDelegate d1 = TakesAWhile;

IAsyncResult ar = d1.BeginInvoke(1,3000,null,null);

while(ar.IsCompleted ==false ){

Console.Write(".");

Thread.Sleep(50);

}

int result = d1.EndInvoke(ar);

Console.WriteLine("Res:"+result);

}

C#里面的反射和特性

在C#中反射的实现是使用System.Type类实现的.

 System.Type类部分成员

成员  成员类型  描述

Name  属性  返回类型的名字

Namespace  属性  返回包含类型声明的命名空间

Assembly  属性  返回声明类型的程序集。

GetFields  方法  返回类型的字段列表

GetProperties  方法  返回类型的属性列表

GetMethods  方法  返回类型的方法列表

获取Type对象有两种方式

1,Type t = myInstance.GetType();//通过类的实例来获取Type对象

在object类有一个GetType的方法,返回Type对象,因为所有类都是从object继承的,所以我们可以在任何类型上使用GetType()来获取它的Type对象

2,Type t = typeof(ClassName);//直接通过typeof运算符和类名获取Type对象

获取里面的属性

FieldInfo[] fi = t.GetFields();

foreach(FieldInfo f in fi){

Console.WriteLine(f.Name+” “);

}

Assembly类在System.Reflection命名空间中定义,它允许访问给定程序集的元数据,它也包含了可以加载和执行程序集。

如何加载程序集?

1,Assembly assembly1 = Assembly.Load(“SomeAssembly”);根据程序集的名字加载程序集,它会在本地目录和全局程序集缓存目录查找符合名字的程序集。

2,Assembly assembly2 = Assembly.LoadFrom(@”c:\xx\xx\xx\SomeAssembly.dll”)//这里的参数是程序集的完整路径名,它不会在其他位置搜索。

1,获取程序集的全名 string name = assembly1.FullName;

2,遍历程序集中定义的类型   Type[] types = theAssembly.GetTypes();

foreach(Type definedType in types){

//

}

3,遍历程序集中定义的所有特性(稍后介绍)

Attribute[] definedAttributes = Attribute.GetCustomAttributes(someAssembly);

什么是特性?

特性(attribute)是一种允许我们向程序的程序集增加元数据的语言结构。它是用于保存程序结构信息的某种特殊类型的类。

将应用了特性的程序结构叫做目标

设计用来获取和使用元数据的程序(对象浏览器)叫做特性的消费者

.NET预定了很多特性,我们也可以声明自定义特性

应用特性

先看看如何使用特性。特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集。我们可以通过把特性应用到结构来实现。

1.在结构前放置特性片段来应用特性;
2.特性片段被方括号包围,特性片段包括特性名和特性的参数列表;
3.应用了特性的结构成为特性装饰。

案例1

[Serializable]  //特性

public class MyClass{

// …

}

案例2

[MyAttribute(“Simple class”,”Version 3.57″)]  //带有参数的特性

public class MyClass{

//…

}

Obsolete特性->.NET预定义特性

一个程序可能在其生命周期中经历多次发布,而且很可能延续多年。在程序生命周期的后半部分,程序员经常需要编写类似功能的新方法替换老方法。处于多种原因,你可能不再使用哪些调用过时的旧方法的老代码。而只想用新编写的代码调用新方法。旧的方法不能删除,因为有些旧代码也使用的旧方法,那么如何提示程序员使用新代码呢?可以使用Obsolete特性将程序结构标注为过期的,并且在代码编译时,显示有用的警告信息。

class Program{

[Obsolete(“Use method SuperPrintOut”)]  //将特性应用到方法

static void PrintOut(string str){

Console.WriteLine(str);

}

[Obsolete(“Use method SuperPrintOut”,true)]//这个特性的第二个参数表示是是否应该标记为错误,而不仅仅是警告。

static void PrintOut(string str){

Console.WriteLine(str);

}

static void Main(string[] args){

PrintOut(“Start of Main”);

}

}

Conditional特性

Conditional特性允许我们包括或取消特定方法的所有调用。为方法声明应用Conditional特性并把编译符作为参数来使用。

定义方法的CIL代码本身总是会包含在程序集中,只是调用代码会被插入或忽略。

#define DoTrace

class Program{

[Conditional(“DoTrace”)]

static void TraceMessage(string str){

Console.WriteLine(str);

}

static void Main(){

TraceMessage(“Start of Main”);

Console.WriteLine(“Doing work in Main.”)

TraceMessage(“End of Main”);

}

}

调用者信息特性

调用者信息特性可以访问文件路径,代码行数,调用成员的名称等源代码信息。

1.这个三个特性名称为CallerFilePath,CallerLineNumber和CallerMemberName
2.这些特性只能用于方法中的可选参数

public static void PrintOut(string message,[CallerFilePath] string filename=””,[CallerLineNumber]int lineNumber = 0,[CallerMemberName]string callingMember=””){

Console.WriteLine(“Message:”+message);

Console.WriteLine(“Line :”+lineNumber);

Console.WriteLine(“Called from:”+callingMember);

Console.WriteLine(“Message :”+message);

}

DebuggerStepThrough特性

我们在单步调试代码的时候,常常希望调试器不要进入某些方法。我们只想执行该方法,然后继续调试下一行。DebuggerStepThrough特性告诉调试器在执行目标代码时不要进入该方法调试。有些方法小并且毫无疑问是正确的,在调试时对其反复单步调试只能徒增烦恼。要小心使用该特性,不要排除了可能出现bug的代码。

该特性位于System.Diagnostics命名空间下

该特性可用于类,结构,构造方法,方法或访问器

class Program{

int _x=1;

int X{

get{return _x;};

[DebuggerStepThrough]

set{

_x=_x*2;

_x+=value;

}

}

public int Y{get;set;}

[DebuggerStepThrough]

void IncrementFields(){

X++;

Y++;

}

static void Main(){

Program p = new Program();

p.IncrementFields();

p.X = 5;

Console.WriteLine(“P.X:”+p.X+” p.Y:”+p.Y);

Console.ReadKey();

}

}

其他预定义特性

特性                     意义

CLSCompliant  声明可公开的成员应该被编译器检查是否符合CLS。兼容的程序集可以被任何.NET兼容的语言使用

Serializable  声明结构可以被序列化

NonSerialized  声明结构不可以被序列化

DLLImport  声明是非托管代码实现的

WebMethod  声明方法应该被作为XML Web服务的一部分暴露

AttributeUsage  声明特性能应用到什么类型的程序结构。将这个特性应用到特性声明上

多个特性

我们可以为单个结构应用多个特性。有下面两种添加方式

独立的特性片段相互叠在一起

[Serializable]

[MyAttribute(“Simple class”,”Version 3.57″)]

单个特性片段,特性之间使用逗号间隔

[Serializable,MyAttribute(“Simple class”,”Version 3.57″)]

 全局特性

我们可以通过使用assembly和module目标名称来使用显式目标说明符把特性设置在程序集或模块级别。

程序集级别的特性必须放置在任何命名空间之外,并且通常放置在AssemblyInfo.cs文件中

AssemblyInfo.cs文件通常包含有关公司,产品以及版权信息的元数据。

[assembly: AssemblyTitle(“ClassLibrary1”)]

[assembly: AssemblyDescription(“”)]

[assembly: AssemblyConfiguration(“”)]

[assembly: AssemblyCompany(“”)]

[assembly: AssemblyProduct(“ClassLibrary1”)]

[assembly: AssemblyCopyright(“Copyright ©  2015”)]

[assembly: AssemblyTrademark(“”)]

[assembly: AssemblyCulture(“”)]

 自定义特性

应用特性的语法和之前见过的其他语法很不相同。你可能会觉得特性跟结构是完全不同的类型,其实不是,特性只是某个特殊结构的类。所有的特性类都派生自System.Attribute。

声明一个特性类和声明其他类一样。有下面的注意事项

声明一个派生自System.Attribute的类

给它起一个以后缀Attribute结尾的名字

(安全起见,一般我们声明一个sealed的特性类)

特性类声明如下:

public sealed class MyAttributeAttribute : System.Attribute{

特性类的公共成员可以是

字段

属性

构造函数

构造函数

特性类的构造函数的声明跟普通类一样,如果不写系统会提供一个默认的,可以进行重载

构造函数的调用

[MyAttribute(“a value”)]  调用特性类的带有一个字符串的构造函数

[MyAttribute(“Version 2.3″,”Mackel”)]  //调用特性类带有两个字符串的构造函数

构造函数的实参,必须是在编译期间能确定值的常量表达式

如果调用的是无参的构造函数,那么后面的()可以不写

构造函数中的位置参数和命名参数

public sealed class MyAttributeAttribute:System.Attribute{

public string Description;

public string Ver;

public string Reviewer;

public MyAttributeAttribute(string desc){

Description = desc;

}

}

//位置参数(按照构造函数中参数的位置)

//命名参数,按照属性的名字进行赋值

[MyAttribute(“An excellent class”,Reviewer = “Amy McArthur”,Ver=”3.12.3″)]

限定特性的使用

有一个很重要的预定义特性可以用来应用到自定义特性上,那就是AttributeUsage特性,我们可以使用它来限制特性使用在某个目标类型上。

例如,如果我们希望自定义特性MyAttribute只能应用到方法上,那么可以用如下形式使用AttributeUsate:

[AttributeUsage(AttributeTarget.Method)]

public sealed class MyAttributeAttribute : System.Attribute{

AttributeUsage有是三个重要的公共属性如下:

ValidOn  保存特性能应用到的目标类型的列表,构造函数的第一个参数就是AttributeTarget  类型的枚举值

Inherited  一个布尔值,它指示特性是否会被特性类所继承  默认为true

AllowMutiple  是否可以多个特性的实例应用到一个目标上 默认为false

事件和委托的区别

之前对事件和委托的概念一直很模糊,虽然知道怎么用,但是感觉理解还不是很透彻的.

现在总算是理解了,具体总结如下.

事件:

1.是一种特殊的委托

2.它不能在方法内定义

3.它不能在所定义的类外所调用

-事件是一种特殊的委托,或者说是受限制的委托,是委托一种特殊应用,只能施加+=,-=操作符。二者本质上是一个东西。

-event ActionHandler Tick; // 编译成创建一个私有的委托示例, 和施加在其上的add, remove方法.

-event只允许用add, remove方法来操作,这导致了它不允许在类的外部被直接触发,只能在类的内部适合的时机触发。委托可以在外部被触发,但是别这么用。

-使用中,委托常用来表达回调,事件表达外发的接口。

-委托和事件支持静态方法和成员方法, delegate(void * pthis, f_ptr), 支持静态返方法时, pthis传null.支持成员方法时, pthis传被通知的对象.

-委托对象里的三个重要字段是, pthis, f_ptr, pnext, 也就是被通知对象引用, 函数指针/地址, 委托链表的下一个委托节点.

事件(event)基于委托,为委托提供了一个发布/订阅机制,我们可以说事件是一种具有特殊签名的委托。

什么是事件?

事件(Event)是类或对象向其他类或对象通知发生的事情的一种特殊签名的委托.

事件的声明

public event 委托类型 事件名;

事件使用event关键词来声明,他的返回类值是一个委托类型。

通常事件的命名,以名字+Event 作为他的名称,在编码中尽量使用规范命名,增加代码可读性。

为了更加容易理解事件,我们还是以前面的动物的示例来说明,有三只动物,猫(名叫Tom),还有两只老鼠(Jerry和Jack),当猫叫的时候,触发事件(CatShout),然后两只老鼠开始逃跑(MouseRun)。接下来用代码来实现。(设计模式-观察者模式)

正则表达式匹配

在C#中使用正则表达会大大提高开发效率.如当你要匹配一个IPV4的地址的时候

 


string ip4 = "192.168.1.5";//你要匹配的地址
 string pattern4 = @"^((([01]?\d\d?|2[0-4]\d|25[0-5])\.){3}([01]?\d\d?|2[0-4]\d|25[0-5]))$";//匹配公式

bool  isIP=Regex.IsMatch(ip4,pattern4);//判断是否匹配成功;

Console.WriteLine(isIP);

string str1 = @"^\d*$";//正则表达式表示一个数字字符串;
 string str2="1s";//Console.ReadLine();
 bool flag=Regex.IsMatch(str2, str1);//只要有一个符合则认为是满足的

string str3 = "how to solve the problem has become to hot issue among many people";
 string pattern2=@"[^how]";
 Console.WriteLine(Regex.Replace(str3,pattern2,"*"));//把str3中出现的h,o,w字符替换为*;

lambada表达式

lambada 表达式在C#里面也是一种匿名方法的表达方式.如下


Func<int, int, int> plus1 = (a, b) =>//lambada表达式的参数是不需要声明类型的;
 {

return a+b;
 };
 Console.WriteLine(plus(5,6));//输出11;
 Func<int, int> pl = a => a + 1;//当参数只有一个的时候可以不用括号,当函数体中只有一行语句的时候也不需要大括号如果有返回值将自动返回这个值;

Console.WriteLine(pl(5));//输出6
通过Lambada表达式可以访问Lamdada表达式块的外部的变量,但是这个使用要很谨慎,因为变量很容易受到外部影响.

&nbsp;

Func<int, int, int> plus =delegate(int a,int b)//匿名方法;
 {

return a+b;

};

Console.WriteLine(pl(5,6));//输出11

c#设计模式—-工厂方法模式

工厂方法模式是一种类创建型模式.在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类.

工厂方法模式包含以下4个角色:

(1)抽象产品:它是定义产品的接口.

(2)具体产品:它是实现抽象产品的接口的类.

(3)抽象工厂:在抽象工厂类或接口中声明了工厂方法,用于返回一个产品.抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口.

(4)具体工厂:它是抽象工厂类的子类.

以下为一个实例的演示代码:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
 interface Logger//抽象产品接口
 {
 void WriteLog();
 }
 class DatabaseLogger : Logger//数据库日志记录类
 {
 public void WriteLog()
 {
 Console.WriteLine("数据库日志记录");

}

 }
 class FileLogger : Logger//文件日志记录类
 {
 public void WriteLog()
 {
 Console.WriteLine("文件日志记录");

}

}

interface LoggerFactory//抽象工厂接口
 {

Logger CreateLogger();//抽象工厂方法

}
 class DatabaseLoggerFactory : LoggerFactory//数据库日志记录工厂类
 {
 public Logger CreateLogger()
 {
 Logger logger = new DatabaseLogger();

return logger;

}

 }
 class FileLoggerFactory : LoggerFactory//文件日志记录工厂类
 {

public Logger CreateLogger()
 {
 Logger logger = new FileLogger();

return logger;
 }

}
 class Program
{

 static void Main(string[] args)
 {
 LoggerFactory factory;//定义抽象工厂类对象引用;
 Logger logger;//定义产品抽象类对象引用;
 factory = new FileLoggerFactory();//创建文件日志记录工厂对象;
 logger = factory.CreateLogger();//创建文件日志记录对象
 logger.WriteLog();
 factory = new DatabaseLoggerFactory();//创建数据库日志记录工厂对象;
 logger = factory.CreateLogger();//创建数据库日志记录对象
 logger.WriteLog();
 Console.ReadKey();

}

 }
}

运行结果如下:

c#中的IEnumerable和IEnumerator

  • 之前一直不是很清楚IEnumerable和IEnumerator 具体区别 今天特地为此编写了一个自己 枚举类 继承自IEnumerable和IEnumerator 接口,我发现如果只继承IEnumerable而不继承IEnumerator 接口 是不可以实现foreach循环访问的因为使用foreach循环必须实现GetEumerator函数.IEnumerable是声明该类可以使用foreach循环访问,而IEnumerator是枚举器具体实现,也就是说 如果类继承了IEnumerable接口则表明此类对象是可以枚举的,具体怎么枚举则需要继承并实现IEnumerator接口.

public IEnumerator GetEnumerator()
{

return (IEnumerator)this;

}

另外我还发现,IEnumerator 接口的2个函数和一个属性 中 MoveNext()是自动调用执行的,Current是自动访问的,而且顺序是 使用foreach循环时首先执行MoveNext()函数把索引往后移一位,然后访问Current当前属性的值.所以当你继承这2个接口时索引定义要从 -1开始这样第一个访问的才能是数组索引为零的元素.

以下是我的实验代码和结果:


using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using System.Collections;
 namespace test
 {
 struct point
 {
 public int x;
 public int y;
 public int z;
 }

class Myenum : IEnumerable, IEnumerator
 {
 private int index;
 private point[] points;

public Myenum(int numofpoint)
 {
 this.index = -1;
 points = new point[numofpoint];

for (int i = 0; i < points.Length; i++)
 {
 points[i].x = i;
 points[i].y = i * i;
 points[i].z = i * i * i;
 }
 }
 public IEnumerator GetEnumerator()
 {

return (IEnumerator)this;

}
 public object Current
 {
 get
 {
 Console.WriteLine("Current");
 return points[index];
 }
 }
 public bool MoveNext()
 {

index++;

if (index < points.Length&&index>=0)
 {
 Console.WriteLine("MoveNext()");
 return true;
 }
 else
 return false;

}
 public void Reset()
 {
 index = -1;
 Console.WriteLine("Reset()");
 }

}
 class Program
 {
 static void Main(string[] args)
 {

Myenum enum1 = new Myenum(5);

foreach (point p in enum1)
 {
 Console.WriteLine("(" + p.x.ToString() + "," + p.y.ToString() + "," + p.z.ToString() + ")");
 }

Console.Read();

}
 }
 }

c#基础学习

1.readonly 保护的是变量的位置不会在构造函数之外被改变.

2.c#的参数类型有4种:值参数,引用参数,输出参数和参数数组.

3.输出参数声明的修饰符是out,out参数是用ref参数加上元数据里的一个特殊属性.

4.参数数组允许向方法传递不定长的参数,声明它的修饰符是params,只有方法的最后一个参数才可以是参数数组,并且参数数组的类型必须是一维的数组.

5.在声明抽象方法时要加上abstract修饰符,并且只允许在同样被声明为abstract的类中声明抽象方法,所有非抽象的继承类都必须重写抽象方法.

6.如果构造函数的声明包含了static 修饰符,它就变成了一个静态的构造函数.

7.属性是字段的一种自然延伸,属性不代表存储的位置,它只是提供了访问的机制.

8.结构体是值类型,一个构造类型的变量之间存储了结构的数据,而一个类类型保存的是指向动态分配对象的引用.