理論上PHP與C#完全沒有關係,因為兩者都是後端開發語言,但因為Laravel台灣 社團要一起參加陳仕傑 老師的自動測試與TDD實務開發(使用C#) 課程,而上課用的是C#,為了讓原本寫PHP的社友能快速看懂C#範例,進而將TDD用在PHP上,因此有了本文介紹。
本文僅就物件導向與常用的部分快速導覽,並不包含PHP與C#語言部分的全部,目的只讓PHP開發者能快速看懂C#學習TDD。
Version
PHP 5.6.10 C# 5.0
Function
PHP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function func1 ($num ) { ... } function func2 (array $arr ) { ... } function func3 (Closure $cl ) { ... } function func4 (Foo $obj ) { ... }
使用function宣告function。
沒有回傳型別。1 1 PHP 7有回傳型別。
boolean、integer、double、string在argument不用指定型別。2 2 PHP 7可指定型別。
在argument可對array加上array type hint。
在argument可對closure加上Closure type hint。
在argument可對object加上interface或class作為type hint。
C# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void func1 (int num ) { ... } int func2 (int [] arr ) { ... } delegate void MyClosure (int name ) ;int func3 (MyClosure cl ) { ... } int func4 (Foo obj ) { ... }
不使用 function宣告function。
需指定回傳型別,若無傳回值則宣告為void。
boolean、integer、double、string在argument需指定型別。
在argument需對array加上型別宣告。
在argument需對closure加上型別宣告,需先使用delegate宣告closure型別。
在argument需對object加上interface或class做型別宣告。
Function Arguments
Pass by Value 當傳值進function時,會重新複製一份進去。PHP 1 2 3 4 5 6 7 8 9 10 11 function func ($num ) { $num = 20 ; } $num = 10 ;func($num ); echo ($num );
boolean、integer、double、string預設都是pass by value。
C# 1 2 3 4 5 6 7 8 9 10 11 void func (int num ) { num = 20 ; } int num = 10 ;func(num); Console.WriteLine(num.ToString());
boolean、integer、double、string預設都是pass by value。
Pass by Reference PHP 1 2 3 4 5 6 7 8 9 10 11 function func (&$num ) { $num = 20 ; } $num = 10 ;func($num ); echo ($num );
若要將boolean、integer、double、string以pass by reference傳進去,在argument需加上&。
C# 1 2 3 4 5 6 7 8 9 10 11 void func (ref int num ) { num = 20 ; } int num = 10 ;func(ref num); Console.WriteLine(num);
若要將boolean、integer、double、string以pass by reference傳進去,在argument需加上ref,且parameter也要加上ref。
Pass Array PHP 1 2 3 4 5 6 7 8 9 10 11 12 13 function func (array $arr ) { $arr [0 ] = 4 ; } $arr = [1 , 2 , 3 ];func($arr ); array_walk($arr , function ($value ) { echo ($value ); });
array預設是pass by value。3 3 PHP傳遞array時,使用的是copy-on-write機制,也就是array先以pass by reference傳進去,當需要寫入時,才將值以pass by value傳入。所以整體看起來像是pass by value,但實際是以pass by reference實作,所以整體效率比單純pass by value高。
使用array type hint.
copy-on-write機制並不是萬靈丹,若需要以pass by reference傳遞array,一樣是加上&。
C# 1 2 3 4 5 6 7 8 9 10 11 12 13 void func (int [] arr ) { arr[0 ] = 4 ; } int [] arr = new int [] { 1 , 2 , 3 };func(arr); Array.ForEach(arr, x => { Console.Write(x); });
array預設是pass by reference。
使用int []宣告array型別,因為array為object,所以要使用new。
Pass Object PHP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class MyClass { public $name ; } function func (Foo $obj ) { $obj ->name = 'John' ; } $obj = new MyClass();$obj ->name = 'Sam' ;func($obj ); echo ($obj ->name);
object預設是pass by reference。
可使用interface或class作為argument的type hint。
C# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class MyClass { public string name; } public func (Foo obj ) { obj.name = "John" ; } Foo obj = new MyClass(); obj.name = "Sam" ; func(obj); Console.WriteLine(obj.name);
object預設是pass by reference。
需使用interface或class作為argument的型別宣告。
Namespace
宣告namespace。4 4 詳細請參考如何使用Namespace? PHP 1 2 3 4 5 6 7 8 namespace MyName \SubName ;class MyClass { ... } use MyName \SubName \MyClass ;
宣告namespace :
使用namespace宣告namespace。
巢狀namespace不須使用括號。
class不須放在namespace內。
引用namespace :
引用class :
namespace與subnamespace之間用\。
使用use關鍵字,直接指定class的完整路徑。
C# 1 2 3 4 5 6 7 8 9 10 namespace MyName.SubName { class MyClass { ... } } using MyName.SubName;using MyName.SubName.MyClass;
宣告namespace :
一樣使用namespace宣告namespace。
巢狀namespace不須使用括號。
class須放在namespace內。
引用namespace :
使用using關鍵字。
namespace與subnamespace之間用.,路徑不包含到class名稱,類似path概念。
引用class :
使用using關鍵字,直接指定class的完整路徑。
Class
宣告class。PHP 1 2 3 4 5 6 7 8 9 10 11 class MyClass { public $var = 'a default value' ; public function displayVar () { return $this ->var; } }
使用class宣告class。
$this表示this object。
object與property/method之間使用->。
C# 1 2 3 4 5 6 7 8 9 10 11 public class MyClass { public string var = "a default value" ; public string displayVar ( ) { return this .var ; } }
需加上public,預設為internal,表只有目前namespace內部可用。
一樣使用class宣告class。
this表示this object。
object與property/method之間使用.。
Class Constant
Class內的常數。PHP 1 2 3 4 5 6 7 8 9 class MyClass { const CONSTANT = 'constant value' ; } echo MyClass::CONSTANT;
使用const定義常數。
不用建立物件,直接使用::讀取常數。5 5 ::稱為scope resolution operator,專門用來存取static與constant。
C# 1 2 3 4 5 6 7 8 9 10 11 12 public MyClass{ public const string CONSTANT = "constant value" ; public readonly int number = 2 ; } MyClass obj = new MyClass(); Console.WriteLine(obj.CONSTANT); Console.WriteLine(obj.number.ToString());
使用const定義常數,與PHP的const相同。
C#另外提供readonly,可以在constructor定義常數。
必須建立物件才能使用常數。
Property
物件內的變數。PHP 1 2 3 4 5 6 class MyClass { public $var1 ; protected $var2 ; private $var3 ; }
提供public,protected與private 3種scope。
C# 1 2 3 4 5 6 class MyClass { public int var1; protected string var2; private bool var3; }
C#正式名稱為field。
一樣提供public,protected與private 3種scope。
Getter & Setter
透過getter與setter對class的protected/private property做讀寫。PHP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class MyClass { protected $name ; public function getName () { return $this ->name; } public function setName ($name ) { $this ->name = $name ; } } $obj = new MyClass();$obj ->setName('Sam' );echo ($obj ->getName());
可分別寫getVar()與setVar($val)達成getter與setter。
C# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class MyClass { protected string name; public string Name { get { return this .name; } set { this .name = value ; } } } MyClass obj = new MyClass(); obj.Name = "Sam" ; Console.WriteLine(obj.Name);
C#正式名稱為property。
value為setter中內建的關鍵字。
New
建立物件。
PHP
C# 1 MyClass obj = new MyClass();
Constructor
使用new建立物件時所呼叫的函式,可以用來初始化物件或實現Dependency Injection。
PHP 1 2 3 4 5 6 7 class MyClass { function __construct () { ... } }
使用__construct() magic method當construcor。
不需要加public。
只能使用單一constructor,不提供constructor overloading。
C# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class MyClass { public MyClass ( ) { ... } public MyClass (int a1 ) { ... } public MyClass (int a1, int a2 ) { ... } public MyClass (int a1, int a2, int a3 ) { ... } }
使用與class名稱相同的method作為constructor。6 6 一般OOP語言都是使用與class名稱相同的method作為constructor。
Constructor沒有回傳型別,只需加上public即可。
若要寫不同參數個數的constructor,一樣使用相同的函式名稱,只要參數不一樣即可,提供constructor overloading。
Static
不用建立物件,就可使用class的property與method。PHP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class MyClass { public static $myStatic = 'foo' ; public static function aStaticMethod () { return self ::$myStatic ; } } echo (MyClass::$myStatic );echo (MyClass::aStaticMethod());foo foo
Property或method加上了static,就變成static property或static method。
在class內部無法再使用$this,必須搭配self::才能存取static property與static method。7 7 ::稱為scope resolution operator,專門用來存取static與constant。
不用使用new建立物件,即可使用class名稱::存取static property與static method。
C# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class MyClass { public static string myStatic = "foo" ; public static string aStaticMethod ( ) { return MyClass.myStatic; } } Console.WriteLine(MyClass.myStatic); Console.WriteLine(MyClass.aStaticMethod()); foo foo
Fields或method加上了static,就變成static fields或static method。
在class內部一樣使用class名稱.存取static field與static method。
不用使用new建立物件,即可使用class名稱.存取static field與static method。
Inheritance
繼承class。PHP 1 2 3 4 class ChildClass extends ParentClass { ... }
使用extends繼承其他class。8 8 Java也是使用extends關鍵字。
C# 1 2 3 4 public class ChildClass : ParentClass { ... }
Abstract Class
宣告成欲重複使用的最頂層class,目的為提供框架供其他class繼承,因此無法被new成物件。
PHP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 abstract class AbstractClass { protected $amout = 10 ; abstract protected function getValue () ; public function printOut () { echo ($this ->getValue()); } } class ConcreteClass extends AbstractClass { protected funciton getValue() { return $this ->amount; } } $obj = new ConcreteClass();$obj ->printOut();
使用abstract宣告的class,無法被new,只能被extends。
使用abstract宣告的method,只能類似interface般被定義,無法實作,因為要留給extends的class去實作。
abstract必須寫在public, protected與private前面。9 9 這是PSR-2所規定,詳細請參考PSR-2 PHP Coding Style
C# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 abstract class AbstractClass { protected int amout = 10 ; protected abstract int getValue ( ) ; public void printOut ( ) { Console.WriteLine(this .getValue()); } } class ConcreteClass : AbstractClass { protected override int getValue ( ) { return this .amount; } } AbstractClass obj = new ConcreteClass(); obj.printOut();
使用abstract宣告的class,無法被new,只能被:繼承。
使用abstract宣告的method,只能類似interface般被定義,無法實作,因為要留給:繼承的class去實作。
class去實作abstract method時,必須加上override,明確表示複寫abstract class所定義的method。
Virtual / Override
PHP並沒有virtual與override概念,也就是每個method天生就是virtual,每個method都可以被override。
PHP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class ParentClass { public function say () { echo ('Hello' ); } } class ChildClass extends ParentClass { public funciton say() { parent ::say(); echo ('Hi' ); } } $obj = new ChildClass();$obj ->say();
繼承的class可以直接修改原本class的method。
使用prarent::存取parent物件的property或method。
C# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class ParentClass { public virtual void say ( ) { Console.WriteLine("Hello" ); } } public class ChildClass : ParentClass { public override void say ( ) { base .say(); Console.WriteLine("Hi" ); } } ParentClass obj = new ChildClass(); $obj.say();
繼承的class不可直接修改原本class的method。
class的method若要被繼承的class所修改,必須先宣告為virtual。
繼承的class若要修改原本class的method,必須再宣告為override。
使用base.存取parent物件的property或method。
Overload
PHP的overload與一般OOP語言所指的overload完全不同。一般OOP語言指的是相同名稱的method/function可以有不同型別的參數或參數個數,而PHP指的是動態建立property或method。10 10 C語言與PHP都不允許 相同名稱的function/method有不同型別的參數或參數個數,只能另外取不同名稱的function/method。
Property Overloading
__get() : 當試圖存取未定義或protected/private的property時,會觸發__get()。
__set() : 當試圖寫入未定義或protected/private的property時,會觸發__set()。
PHP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class MyClass { private $data = []; function __set ($name , $value ) { $this ->data[$name ] = $value ; } function __get ($name ) { if (array_key_exists($name , $this ->data)) { return $this ->data[$name ]; } } } $obj = new MyClass();$obj ->No = 1 ;$obj ->Name = 'Sam' ;echo ($obj ->No);echo ($obj ->Name);
20行/21行動態定義出新的property。
使用__get()與__set()存取未定義的property。
將未定義的property存在陣列內,其中array key為property name,array value為property value。
透過__get()達成getter,透過__set()達成setter,需搭配使用array_key_exists()判斷property是否存在。
PHP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class MyClass { protected $name ; public function __get ($property ) { if (property_exists($this , $property )) { return $this ->$property ; } } public function __set ($property , $value ) { if (property_exists($this , $property )) { $this ->$property = $value ; } return $this ; } } $obj = new MyClass();$obj ->name = 'Sam' ;echo ($obj ->name);
使用__get()與__set()存取protected/private的property。
透過__get()達成getter,透過__set()達成setter,需搭配使用property_exists()判斷property是否存在。
可寫出完全C#風格的property。
Method Overloading
__call() : 當試圖呼叫未定義或protected/private的method時,會觸發__call()。11 11 PHP 5.3可使用closure的__invoke(),詳細請參考深入探討bindTo() 。
PHP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Foo { public function __call ($method , $args ) { if (is_callable([$this , $method ])) { return call_user_func_array($this ->$method , $args ); } } } $obj = new Foo('Sam' );$obj ->Hello = function () { return 'Hello World' ; }; echo ($obj ->Hello());
13行動態定義出新的method。
使用__call()存取未定義的method,需搭配is_callable()判斷method是否存在,若存在使用call_user_func()或call_user_func_array()呼叫新定義的method。12 12 這裏不能使用method_exists(),會判斷不到動態定義的method。
C# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class MyClass { public string Say (string name ) { return "Hello " + name; } public string Say (int no, string name ) { return "Hello No:" + Convert.toString(no) + " " + name; } } MyClass obj = new MyClass(); Console.WriteLine(obj.Say("Sam" )); Console.WriteLine(obj.Say(1 , "Sam" ));
Overload允許相同名稱的Say(),只要參數型別或個數不一樣即可。
Final
繼承的class無法再override原本class的method。
PHP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class ParentClass { public function test () { echo ('Test' ); } final public funciton moreTest() { echo ('ParentClass Test' ); } } class ChildClass extends ParentClass { public function moreTest () { echo ('Child Test' ); } }
使用final宣告的method,若繼承的class試圖去override,將出現錯誤訊息。13 13 Java也是使用final關鍵字。
final必須寫在public之前。14 14 這是PSR-2所規定,詳細請安考PSR-2 PHP Coding Style
C# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class ParentClass { public void test ( ) { Console.WriteLine("Test" ); } public sealed void moreTest ( ) { Console.WriteLine("ParentClass Test" ); } } public class ChildClass : ParentClass { public void moreTest ( ) { Console.WriteLine("ChildClass Test" ); } }
使用sealed宣告method,若繼承的class試圖去override,將會出現錯誤訊息。
Interface
宣告成各class之間合作的抽象介面,目的為提供將來class的擴充使用。15 15 詳細請參考如何使用Interface?
PHP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 interface IDocumentable { public function getContent () ; } class HtmlDocument implements IDocumentable { public function getContent () { ... } } class Store { protected $shelf = []; public function addDocument (IDocumentable $book ) { $this ->shelf[] = $book ; } public function show () { foreach ($this ->shelf as $value ) { echo ($value ); } } } $watsons = new Store();$watsons ->addDocument(new HtmlDocument());$watsons ->show();
宣告interface :
使用interface宣告interface。
interface不必宣告為public,但函式必須宣告為public。
實踐interface :
使用implements實踐interface。16 16 Java也是使用implements關鍵字。
使用interface :
可搭配interface type hint,只有實踐該interface的物件能傳入。
C# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 interface IDocumentable { string getContent ( ) ; } class HtmlDocument : IDocumentable { public string getContent ( ) { ... } } class Store { protected List<IDocumentable> shelf = new List<IDocumentable>(); public void addDocument (IDocumentable book ) { this .shelf.Add(book); } public void show ( ) { foreach (string value in this .shelf) { Console.WriteLine(value ); } } } Store watsons = new Store(); watsons.addDocument(new HtmlDocument()); watsons.show();
宣告interface :
一樣使用interface宣告interface。
interface與函式都不用宣告public。
實踐interface :
使用interface :
須明確指定interface型別,只有實踐該interface的物件能傳入。
Closure
callback,closure,anonymous function與callable在PHP講的是同一件事情。17 17 詳細請參考如何使用Closure?
PHP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 interface IDocumentable { public function getContent () ; } class HtmlDocument implements IDocumentable { public function getContent () { ... } } class Store { protected $shelf = []; public function addDocument (IDocumentable $book ) { $this ->shelf[] = $book ; } public function show () { array_walk($this ->shelf, function ($value ) { echo ($value ); }); } } $watsons = new Store();$watsons ->addDocument(new HtmlDocument());$watsons ->show();
array_walk()直接傳入closure,不用先定義函式。
C# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 interface IDocumentable { string getContent ( ) ; } class HtmlDocument : IDocumentable { public string getContent ( ) { ... } } class Store { protected List<IDocumentable> shelf = new List<IDocumentable>(); public void addDocument (IDocumentable book ) { this .shelf.Add(book); } public void show ( ) { this .shelf.ForEach(value => Console.WriteLine(value )); } } Store watsons = new Store(); watsons.addDocument(new HtmlDocument()); watsons.show();
C#正式名稱為 lambda 。18 18 C++與Java也稱為 lambda ,JavaScript ES6則稱為arrow function 。
List<>.ForEach()直接傳入lambda,不用先定義函式。
Conclusion
PHP與C#雖然都是C-style的語言,看起來很像,但細看下去還是很多地方不太一樣。
PHP與C#較大差異如下 :
型別 : PHP是弱型別語言,不需宣告型別;C#是強型別語言,需宣告型別。
傳遞array : PHP是pass by value,C#是pass by reference。
Property : PHP稱property,C#稱field。
Getter/Setter : PHP稱Getter/Setter,C#稱為property。
Constructor : PHP使用__construct(),C#使用與class同名的method。
static : PHP使用::,C#使用.。
Inheritance : PHP使用extends,C#使用:。
interface : PHP使用implements,C#使用:。
virtual/override : PHP無,C#需明確指定virtual與override。
overload : PHP指動態建立property或method,C#指相同名稱的method。