理論上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。