日志标签:区别

[Unity3D]脚本中Start()和Awake()的区别

时间:2014年03月04日作者:小侃评论次数:0

Unity3D初学者经常把Awake和Start混淆。

简单说明一下,Awake在MonoBehavior创建后就立刻调用,Start将在MonoBehavior创建后在该帧Update之前,在该Monobehavior.enabled == true的情况下执行。

[javascript] 

  1. void Awake (){
  2. }
  3. //初始化函数,在游戏开始时系统自动调用。一般用来创建变量之类的东西。
  4. void Start(){
  5. }
  6. //初始化函数,在所有Awake函数运行完之后(一般是这样,但不一定),在所有Update函数前系统自动条用。一般用来给变量赋值。

我们通常书写的脚本,并不会定义[ExecuteInEditMode]这个Attribute,所以Awake和Start都只有在Runtime中才会执行。

 

例1:

 

[javascript]

  1. public class Test : MonoBehaviour {
  2.     void Awake () {
  3.         Debug.Log(“Awake”);
  4.         enabled = false;
  5.     }
  6.     void Start () {
  7.         Debug.Log(“Start”);
  8.     }
  9. }

 

以上代码,在Awake中我们调用了enabled = false; 禁止了这个MonoBehavior的update。由于Start, Update, PostUpdate等属于runtime行为的一部分,这段代码将使Start不会被调用到。

在游戏过程中,若有另外一组代码有如下调用:

[javascript]

  1. Test test = go.GetComponent<Test>();
  2. test.enabled = true;

这个时候,若该MonoBehavior之前并没有触发过Start函数,将会在这段代码执行后触发。

例2:

player.cs

[javascript] 

  1. private Transform handAnchor = null;
  2. void Awake () { handAnchor = transform.Find(“hand_anchor”); }
  3. // void Start () { handAnchor = transform.Find(“hand_anchor”); }
  4. void GetWeapon ( GameObject go ) {
  5.     if ( handAnchor == null ) {
  6.         Debug.LogError(“handAnchor is null”);
  7.         return;
  8.     }
  9.     go.transform.parent = handAnchor;
  10. }

other.cs

 

[javascript] 

  1. GameObject go = new GameObject(“player”);
  2. player pl = go.AddComponent<player>(); // Awake invoke right after this!
  3. pl.GetWeapon(weaponGO);

 

以上代码中,我们在player Awake的时候去为handAnchor赋值。如果我们将这步操作放在Start里,那么在other.cs中,当执行GetWeapon的时候就会出现handAnchor是null reference.

总结:我们尽量将其他Object的reference设置等事情放在Awake处理。然后将这些reference的Object的赋值设置放在Start()中来完成。
当MonoBehavior有定义[ExecuteInEditMode]时

当我们为MonoBehavior定义了[ExecuteInEditMode]后,我们还需要关心Awake和Start在编辑器中的执行状况。

当该MonoBehavior在编辑器中被赋于给GameObject的时候,Awake, Start 将被执行。
当Play按钮被按下游戏开始以后,Awake, Start 将被执行。
当Play按钮停止后,Awake, Start将再次被执行。
当在编辑器中打开包含有该MonoBehavior的场景的时候,Awake, Start将被执行。

值得注意的是,不要用这种方式来设定一些临时变量的存储(private, protected)。因为一旦我们触发Unity3D的代码编译,这些变量所存储的内容将被清为默认值。

 

下面再来看看Unity圣典中的解释。

Awake()

当一个脚本实例被载入时Awake被调用。

Awake用于在游戏开始之前初始化变量或游戏状态。在脚本整个生命周期内它仅被调用一次.Awake在所有对象被初始化之后调用,所以你可以安全的与其他对象对话或用诸如 GameObject.FindWithTag 这样的函数搜索它们。每个游戏物体上的Awke以随机的顺序被调用。因此,你应该用Awake来设置脚本间的引用,并用Start来传递信息。Awake总是在Start之前被调用。它不能用来执行协同程序。
Start()

Start仅在Update函数第一次被调用前调用。Start在behaviour的生命周期中只被调用一次。它和Awake的不同是Start只在脚本实例被启用时调用。
你可以按需调整延迟初始化代码。Awake总是在Start之前执行。这允许你协调初始化顺序。

深入解析和破解C#的readonly只读字段及与const的区别

时间:2014年02月28日作者:小侃评论次数:0

 

 

 

 

返回目录

请允许我再唠叨几句const和readonly

readonly 关键字与 const 关键字不同。 const 字段只能在该字段的声明中初始化。 readonly 字段可以在声明或构造函数中初始化。 因此,根据所使用的构造函数,readonly 字段可能具有不同的值。 另外,const 字段为编译时常数,而 readonly 字段可用于运行时常数。

其实大家都懂的,我就不多废话,直接重点:

Const:
1>必须在定义时就进行赋值,并且赋值为常量,不能赋值为变量。
2>const常量隐含是static的,所以只能用类名来进行方法.
3>常量的类型可以为简单类型,枚举类型,各种引用类型(包括string)。这里引用类型的常量,如果不是string类型,则只允许赋以null。常量的类型不能为普通的struct类型。如果要实现struct类型的保持不变的量,可以用readonly。
4>const可以修饰方法内的局部变量。
 
ReadOnly:
1>readonly域可以是各种类型。
2>readonly域可以用变量或表达式进行赋值
3>readonly不能修饰局部变量。
4>readonly域并不隐含static性质。
5>readonly域要求定义时初始化。如果没有初始化,自动取默认值(0,false,null等)。如果要赋值,可以定义时初始化,也可以在构造方法中赋值,包括使用out及ref参数进行处理。但它最多只能被赋值一次。赋值以后,其值不能被修改。
 
可以看出,readonly要求的只读性时考虑”运行时时只读的”,而const要求的只读性是”编译时就是常量”。

 

好了,下面开始修改readonly字段。

 

 

返回目录

计策1:反间计 —— 反射修改

要说什么方法不用我们另外做手脚直接了当快速轻松得修改readonly只读属性莫过于使用.NET中的正规方式,当你直接对readonly字段进行“=”赋值操作然后编译,VS肯定给你个错误拒绝编译。但是另一种动态赋值方式——反射你有没有试过呢???

 

好吧或许你曾经问过某高手说readonly字段能不能被修改,他一定会回答:“笨蛋,这还用问,能修改还叫readonly吗?”。于是聪明人知道了并记住了(或许聪明人根本不会问这样的问题)。但是……一个笨的很笨的小笨他太笨了所以还是想试试,于是先用第一种直接静态赋值方式,VS编译错误。又尝试另一种运行后的动态赋值,于是他成功了!

 

是的,静态方式编译器不允许,但是动态赋值——反射却背叛了它,运行后用反射想怎么改就怎么改!

看下面代码:

class Program

{

static readonly object data = 1;

 

static void Main()

{

Console.WriteLine(data);

mod();

Console.WriteLine(data);

}

 

static void mod()

{

//得到静态的非公有字段信息

var fieldInfo = typeof(Program).GetField(“data”, BindingFlags.Static | BindingFlags.NonPublic);

fieldInfo.SetValue(null, “MOD”);

}

}

 

 

输出:

1

MOD

 

值被成功修改了。

 

 

返回目录

计策2:借刀杀人——调节字段偏移位置的结构体来修改

如果你经常用C#调用非托管代码的话,P/Invoke和各种Marshal操作你会很熟悉。而另一个常用到的System.Runtime.InteropServices的StructLayout和FieldOffset特性是用来定义一个显示的字段内存布局的结构体。

 

没人可以阻止我们把两个字段对在一个位置,然后用一个非readonly字段去彻底控制一个readonly字段!!!

 

代码

using System;

using System.Runtime.InteropServices;

 

namespace Mgen.TTC

{

[StructLayout(LayoutKind.Explicit)]

struct a

{

[FieldOffset(0)]

public string s;

[FieldOffset(0)]

public readonly string READ_ONLY;

//readonly字段

 

public a(string arg)

{

//初始化readonly字段

s = READ_ONLY = arg;

}

}

 

class Program

{

static void Main()

{

//此时oa的readonly字段被初始化成字符串:”常量”

a oa = new a(“常量”);

 

Console.WriteLine(oa.READ_ONLY);

oa.s = “你被改了……”;

Console.WriteLine(oa.READ_ONLY);

}

}

}

输出:

常量

你被改了……

 

很好,这里修改变量s就等价于修改常量字段:READ_ONLY。两个字段是对在同一个内存位置的,然后通过修改一个非readonly字段去间接修改一个readonly字段。

 

 

 

 

返回目录

计策3:无中生有——使用ilasm创建强行修改语句

当你使用Visual Studio(或其他C#编译器)时对readonly字段进行赋值肯定结果是编译错误。于是你很想强行通过这条语句(于是你又非常使劲得按了下Ctrl + F5,结果……),好吧C#编译器都一个样,无法去改变它,但是C#幕后的IL并非如此。接下来让我们用ilasm.exe创建一个对readonly字段进行直接赋值的程序。

ilasm的编译需要一个IL文件,这个IL文件就是文本文件,你可以自己写IL,不过这个太麻烦了,我们可以用IL反编译程序ildasm.exe来把一个托管程序集的IL代码框架先生成,然后导出,最后加入自己的代码再用ilasm编译。

 

首先创建一个C#控制台程序,然后加一个readonly只读字段,编译(这样做我们就不用从头开始写IL)。如下:

class Program

{

static readonly string s = “hehe”;

static void Main(string[] args)

{ }

}

接着用ildasm.exe打开这个程序。

(ildasm在Windows SDK中,Windows SDK路径在:%PROGRAMFILES%\Microsoft SDKs\Windows)

 

在File菜单选择Dump(或直接按Ctrl+D)。选择OK将IL文件保存。

 

然后用文本编辑器打开这个IL文件,找到Main函数(可以通过查找文字:Main)。在主函数.entrypoint指令下加入下列指令:

代表4条C#语句:输出s,修改s,再输出s,Console.Read()。

(注意下面的ConsoleApplication1.Program::s代表s字段的完整命名空间,根据你的程序做适当修改)

.maxstack  8

IL_0000:  ldsfld     string ConsoleApplication1.Program::s              //s进栈

IL_0005:  call       void [mscorlib]System.Console::WriteLine(string)   //输出s

IL_000a:  ldstr      bytearray (AB 88 EE 4F 39 65 )                     //字符串被修改”

IL_000f:  stsfld     string ConsoleApplication1.Program::s              //将”被修改”赋值给s

IL_0014:  ldsfld     string ConsoleApplication1.Program::s              //s进栈

IL_0019:  call       void [mscorlib]System.Console::WriteLine(string)   //输出s

IL_001e:  call       int32 [mscorlib]System.Console::Read()             //Console.Read()

IL_0023:  pop

IL_0024:  ret

 

接下来就用ilasm.exe来编译IL文件。

(ilasm在.NET Framework安装目录下,路径:%SYSTEMROOT%\microsoft.net\framework\)

 

将刚才修改后的IL文件路径作为第一个参数传入ilasm.exe中,ilasm便会将IL编译成可执行文件。然后运行这个exe。

 

程序会输出:

hehe

被修改

 

你可以在Reflector(或ILSpy)中打开这个程序,它的C#代码正好是C#编译器所不允许编译的结果:

internal class Program

{

// Fields

private static readonly string s = “hehe”;

 

// Methods

private static void Main(string[] args)

{

Console.WriteLine(s);

s = “被修改”;

Console.WriteLine(s);

Console.Read();

}

}

 

 

 

 

返回目录

为什么?——翻阅CLI标准:initonly修饰符

 

注意:

下文会引用Common Language Infrastructure (CLI) 标准的内容,如果你想亲自看一下CLI标准的内容。可以在这里下载:http://www.ecma-international.org/publications/standards/Ecma-335.htm

 

 

看完了上面的东西,你一定会问为什么?为什么执行环境没有去有效得保护一个readonly字段?为什么IL允许去修改一个readonly字段而C#不可以?

 

让我们开始分析一个readonly字段。如下C#代码:

readonly int i;

 

接下来,你应该猜到了,又开始IL了……

(汗……别抱怨怎么又得从IL上说起,我也不想这样做,其实IL也挺无奈的,人家很低调的,但总被一些人给不停得揭露出来……仿佛他们研究得很深奥似的……(我在自嘲,没说别人))

 

上面代码会被编译成下面的IL:

.field private initonly int32 i

 

可以看出来,readonly字段就是一个带有initonly的字段,一切的不同就在这个initonly修饰符上。

 

根据CLI标准(ECMA-334),第二部分:元数据(Partition II Metadata),16. 定义和引用字段(16 Defining and referencing fields),1. 字段的属性(Attributes of field),2. 字段约定属性(Field contract attributes)中对initonly修饰符的描述

initonly marks fields which are constant after they are initialized. These fields shall only be mutated inside a constructor. If the field is a static field, then it shall be mutated only inside the type initializer of the type in which it was declared. If it is an instance field, then it shall be mutated only in one of the instance constructors of the type in which it was defined. It shall not be mutated in any other method or in any other constructor, including constructors of derived classes.

[Note: The use of ldflda or ldsflda on an initonly field makes code unverifiable.  In unverifiable code, the VES need not check whether initonly fields are mutated outside the constructors. The VES need not report any errors if a method changes the value of a constant. However, such code is not valid. end note]

上面第一段就是在说readonly的用法:initonly字段在初始化后就成为常量。这种字段只能在构造函数中改变(进行赋值)。如果是静态字段,只能在当前类静态构造函数中或初始设定项。如果是非静态字段,只能在当前类的非静态构造函数中使用(或类型初始设定项),不能在其他方法或者其他构造函数中,包括派生类的构造函数。

 

第二段找到了重点(以Note开始)。对initonly字段使用ldflda和ldsflda会使代码无法验证。在无法通过验证的代码环境中,VES不需要检查initonly字段是否在构造函数外发生改变,VES也不需要报错如果一个方法改变了这个只读字段的值。但是,这种代码不是合法的。

 

其中VES(Virtual Execution System):虚拟执行系统是指代码的运行环境,而微软的CLI执行就是CLR。

 

IL指令:ldflda(ldsflda用于静态字段)用于将字段的地址进栈。当使用ref/out关键字时,或者调用值类型的成员时,CLR都会把相应对象的地址压入栈中。(其实上面提到ldflda/ldsflda是指去修改readonly字段,所以不一定只限于ldflda/ldsflda,当然也没准其他IL指令会间接用到ldflda/ldsflda)

 

OK,那么其实CLI标准没有强制要求执行环境必须要确保readonly只读字段必须遵守行为准则,只要readonly字段被非法修改,代码将会无法验证(unverifiable)。下面的问题就是无法验证的代码又是一个什么概念?

 

 

 

返回目录

捕获MSIL的代码验证错误

当MSIL被JIT编译时,如果程序集没有被赋予跳过代码验证的权限时,那么代码必须要通过代码验证过程,代码验证直接确保了程序的类型安全,使得对象可以绝对隔离并且免受恶意破坏,代码验证所要做的是如下具体3点:

  • 对类型的引用与被引用的类型严格兼容。
  • 在对象上只调用正确定义的操作。
  • 标识与声称的要求一致。

(强烈建议阅读MSDN – 托管执行过程 – 将 MSIL 编译为本机代码 – 代码验证(http://msdn.microsoft.com/zh-cn/library/k5532s8a.aspx))

 

上面提到的“跳过代码验证的权限”就是SecurityPermissionFlag.SkipVerification枚举值,而本机运行的.NET程序集默认是被给予全部权限的,因此会包含这个权限。IL代码验证是不发生的。接下来要做的就是通过创建一个没有“跳过代码验证的”应用程序域,在这个应用程序域中运行上面用IL创建的那个修改只读字段的程序集,看看会发生什么情况。

 

创建另一个控制台应用程序(最好把刚才的IL编译的修改只读字段的程序拷贝到此程序的编译目录下,这样可以用相对路径),然后创建一个应用程序域,并执行刚才的程序集:

static void Main()

{

//创建一个没有跳过代码验证权限的应用程序域

var appdomain = CreateSandbox();

//testApp.exe是刚才用IL编译的修改readonly只读字段的程序的路径

appdomain.ExecuteAssembly(“testApp.exe”);

}

 

static AppDomain CreateSandbox()

{

//初始化空的权限集

var pset = new PermissionSet(PermissionState.None);

//添加Execution(执行)安全权限,没有此权限代码不会执行

pset.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));

//添加其他权限

pset.AddPermission(new FileIOPermission(PermissionState.Unrestricted));

pset.AddPermission(new UIPermission(PermissionState.Unrestricted));

 

 

//必须设置AppDomainSetup.ApplicationBase,否则无法成功创建应用程序域

var adSetup = new AppDomainSetup();

adSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;

 

var domain = AppDomain.CreateDomain(“新的AppDomain”,

null,

adSetup,

pset);

 

return domain;

}

 

 

运行结果:有异常抛出。很好,这正是我们想要的结果:

Unhandled Exception: System.Security.VerificationException: Operation could dest

abilize the runtime.

at ConsoleApplication1()      //用IL编译的程序的主函数

at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args

)

at System.AppDomain.ExecuteAssembly(String assemblyFile, String[] args)

at System.AppDomain.ExecuteAssembly(String assemblyFile)

at System.AppDomain.ExecuteAssembly(String assemblyFile)

at Mgen.TTC.Program.Main()    //当前程序的主函数

可以看到System.Security.VerificationException异常被抛出。”operation could destabilize the runtime”:指操作可能使运行时刻不稳定。因为这个用IL编译的程序集包含上面提到过的“无法验证”的代码,而当前执行的应用程序域又没有被给予“跳过代码验证的安全权限”,因此代码验证此刻会发生,当程序集运行时,JIT边编译边进行代码验证,而觉察到这个“修改只读属性”的语句,它打破了代码验证所强制的运行时刻类型安全,显然破坏了运行时刻稳定性,接着马上抛出VerificationException异常,程序集执行失败。

 

 

如果你把上面的应用程序域的限制权限改成全部权限:

var pset = new PermissionSet(PermissionState.Unrestricted);

重新运行一下程序,这次没有任何异常抛出,另一个程序集的只读字段会继续被修改并输出正确结果。

hehe

被修改

 

 

 

 

返回目录

总结

如果你仔细看了文章,那么“总结”对你来意义不大,因为你肯定已经自己理解了。当然如果你没时间全看完,总结告诉你的是(请从上往下看):

  • readonly对象本质上不是常量,是字段。
  • 标准(CLI标准)没有规定readonly字段不能被修改。
  • 因为标准不强求,更何况readonly对象是字段,所以IL和反射都可以修改它。
  • 当然编译器还是要尽量阻止它被修改(如果编译器都允许它被修改,那readonly这个关键字还有什么用?)
  • 一旦readonly字段被修改了,该代码就属于无法验证代码。
  • MSIL在被JIT编译时会验证代码,如果遇到无法验证代码会抛出异常。
  • 当然有专门的权限可以使程序代码跳过(可以理解成不进行)代码验证。
  • 默认情况下本机程序拥有所有权限,所以修改readonly字段没有异常。当在部分信任的环境下程序集可以收到System.Security.VerificationException代表代码验证异常。

 

C#comboBox属性SelectedItem与SelectedText区别

时间:2013年06月23日作者:小侃评论次数:0

ComboBox使用SelectedItem获取或设置列表中当前选定的对象。这里先谈一下如何设置指定选中项:如果在列表中找到了该对象,则将它显示在ComboBox的编辑部分,并且将SelectedIndex 属性设置为相应的索引。如果列表中不存在该对象,则SelectedIndex属性保留其当前值。 

比如:要在如下ComboBox的编辑部分中选中“闲暇收藏”这一项,使用SelectedItem实现代码如下:

this.ComboBox.SelectedItem = (object)”闲暇收藏”;

//注:SelectedItem是指当前选定项的对象,而不是文本,故写代码时要注意字符串强制转换为‘object’对象类型

C#comboBox属性SelectedItem与SelectedText区别

那该怎么获取当前选定项的文本值呢?方法有二:

方法一:

string   str=(string)comboBox.SelectedItem;

方法二:

string   str=comboBox.Text; 

    
comboBox.SelectedText:表示组合框中当前选定文本的字符串,具体说是指你当前选中第几个字符,不是第几项。比如:“你好吗”如果你用鼠标选中了“好”,那么comboBox.SelectedText就是当前项的第二个字符。比如当前项是:“小青蛙”,那么comboBox.SelectedText的值就是“青”。comboBox的DropDownStyle必须为Simple如果DropDownStyle设置为comboBoxStyle.DropDownList,则返回值为空字符串   (“”)。