以語法角度快速比較PHP與C#

理論上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 1PHP 7有回傳型別。
  • booleanintegerdoublestring在argument不用指定型別。2 2PHP 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
  • booleanintegerdoublestring在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);

// Result:
// 10

  • booleanintegerdoublestring預設都是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());

// Result:
// 10
  • booleanintegerdoublestring預設都是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);

// Result:
// 20
  • 若要將booleanintegerdoublestringpass 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);

// Result:
// 20
  • 若要將booleanintegerdoublestringpass 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);
});

// Result:
// 123
  • array預設是pass by value3 3PHP傳遞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);
});

// Result:
// 423
  • 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);

// Result:
// John
  • 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);

// Result:
// John
  • 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
{

// property declaration
public $var = 'a default value';

// method declaration
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
{
// property declaration
public string var = "a default value";

// method declaration
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;

// Result:
// constant value

  • 使用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());

// Result:
// constant value
  • 使用const定義常數,與PHP的const相同。
  • C#另外提供readonly,可以在constructor定義常數。
  • 必須建立物件才能使用常數。

Property


物件內的變數。

PHP
1
2
3
4
5
6
class MyClass
{

public $var1;
protected $var2;
private $var3;
}

  • 提供public,protectedprivate 3種scope。
C#
1
2
3
4
5
6
class MyClass
{
public int var1;
protected string var2;
private bool var3;
}
  • C#正式名稱為field
  • 一樣提供public,protectedprivate 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());

// Result:
// Sam

  • 可分別寫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);

// Result:
// Sam
  • C#正式名稱為property
  • value為setter中內建的關鍵字。

New


建立物件。

PHP
1
$obj = new MyClass();
  • 不用宣告型別,直接使用new建立物件。
C#
1
MyClass obj = new MyClass();
  • 需要宣告型別,一樣使用new建立物件。

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());

// Result:
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());

// Result:
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 8Java也是使用extends關鍵字。
C#
1
2
3
4
public class ChildClass : ParentClass
{
...
}
  • 使用:繼承其他class。

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;

// Force extending class to define this method
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();

// Result:
// 10
  • 使用abstract宣告的class,無法被new,只能被extends
  • 使用abstract宣告的method,只能類似interface般被定義,無法實作,因為要留給extends的class去實作。
  • abstract必須寫在public, protectedprivate前面。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;

// Force extending class to define this method
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();

// Result:
// 10
  • 使用abstract宣告的class,無法被new,只能被:繼承。
  • 使用abstract宣告的method,只能類似interface般被定義,無法實作,因為要留給:繼承的class去實作。
  • class去實作abstract method時,必須加上override,明確表示複寫abstract class所定義的method。

Virtual / Override


PHP並沒有virtualoverride概念,也就是每個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();

// Result:
// Hello
// Hi
  • 繼承的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();

// Result:
// Hello
// Hi
  • 繼承的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 10C語言與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);

// Result:
// 1
// Sam
  • 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);

// Result:
// Sam
  • 使用__get()__set()存取protected/private的property。
  • 透過__get()達成getter,透過__set()達成setter,需搭配使用property_exists()判斷property是否存在。
  • 可寫出完全C#風格的property。

Method Overloading

  • __call() : 當試圖呼叫未定義protected/private的method時,會觸發__call()11 11PHP 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);
}
// else throw exception
}
}

$obj = new Foo('Sam');
$obj->Hello = function () {
return 'Hello World';
};

echo($obj->Hello());

// Result:
// Hello World
  • 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"));

// Result:
// Sam
// Hello No: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');
}
}

// Results in Fatal error: Cannot override final method ParentClass::moreTest()
  • 使用final宣告的method,若繼承的class試圖去override,將出現錯誤訊息。13 13Java也是使用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");
}
}
// Results in Fatal error: Cannot override sealed method ParentClass.moreTest()
  • 使用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 16Java也是使用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型別,只有實踐該interface的物件能傳入。

Closure


callback,closure,anonymous functioncallable在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#正式名稱為 lambda18 18C++與Java也稱為 lambda,JavaScript ES6則稱為arrow function
  • List<>.ForEach()直接傳入lambda,不用先定義函式。

Conclusion


  • PHP與C#雖然都是C-style的語言,看起來很像,但細看下去還是很多地方不太一樣。
  • PHP與C#較大差異如下 :
    1. 型別 : PHP是弱型別語言不需宣告型別;C#是強型別語言宣告型別。
    2. 傳遞array : PHP是pass by value,C#是pass by reference
    3. Property : PHP稱property,C#稱field。
    4. Getter/Setter : PHP稱Getter/Setter,C#稱為property。
    5. Constructor : PHP使用__construct(),C#使用與class同名的method。
    6. static : PHP使用::,C#使用.
    7. Inheritance : PHP使用extends,C#使用:
    8. interface : PHP使用implements,C#使用:
    9. virtual/override : PHP無,C#需明確指定virtualoverride
    10. overload : PHP指動態建立property或method,C#指相同名稱的method。
2015-09-16