Local functions (區域函式)
有時,輔助函式只有在使用他的函式內才有意義。現在這種情況下,你可以在其他函式內宣告 local functions (區域函式):
public int Fibonacci(int x)
{
if (x < 0) throw new ArgumentException("Less negativity please!", nameof(x));
return Fib(x).current;
(int current, int previous) Fib(int i)
{
if (i == 0) return (1, 0);
var (p, pp) = Fib(i - 1);
return (p + pp, p);
}
}
在 local function (區域函式) 內,可以直接使用封閉區塊內的 parameters (參數) 與 local variables (區域變數),用法及規則就跟 lambda 運算式 的用法一樣。
舉例來說,iterator method 通常外面都需要包覆另一個 non-iterator method ,用來在呼叫時做參數檢查 (iteraotr 在這時並不會執行,而是在 MoveNext() 被呼叫時才會啟動)。這時 local function 就非常適合在這裡使用:
public IEnumerable<T> Filter<T>(IEnumerable<T> source, Func<T, bool> filter)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (filter == null) throw new ArgumentNullException(nameof(filter));
return Iterator();
IEnumerable<T> Iterator()
{
foreach (var element in source)
{
if (filter(element)) { yield return element; }
}
}
}
同樣的例子,不用 local function 的話,就必須把該 method 定義在 Filter 後面,將 iterator 宣告為 private method。這樣會導致封裝性被破壞: 其他成員可能意外的使用它 (而且參數檢查會被略過)。同時,所有原本 local function 需要取用的區域變數與參數,都必須做一樣的處理 (變成 private members)。
public ref int Find(int number, int[] numbers)
{
for (int i = 0; i < numbers.Length; i++)
{
if (numbers == number)
{
return ref numbers; // return the storage location, not the value
}
}
throw new IndexOutOfRangeException($"{nameof(number)} not found");
}
int[] array = { 1, 15, -39, 0, 7, 14, -12 };
ref int place = ref Find(7, array); // aliases 7's place in the array
place = 9; // replaces 7 with 9 in the array
WriteLine(array[4]); // prints 9
這在回傳大型資料結構時相當有用。舉例來說,遊戲程式可能會預先配置龐大的陣列來存放結構資料 (這樣是為了避免執行過程中發生 garbage collect,
導致遊戲暫停)。現在 method 可以直接用參考的方式傳回結構的資料,呼叫端可以直接讀取與修改它的內容。
class Person
{
private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>();
private int id = GetId();
public Person(string name) => names.TryAdd(id, name); // constructors
~Person() => names.TryRemove(id, out *); // destructors
public string Name
{
get => names[id]; // getters
set => names[id] = value; // setters
}
}
這個新語法的範例程式並非來自 Microsoft C# 編譯器的團隊,而是由社群成員貢獻的。太棒了! Open source!
class Person
{
public string Name { get; }
public Person(string name) => Name = name ?? throw new ArgumentNullException(nameof(name));
public string GetFirstName()
{
var parts = Name.Split(" ");
return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!");
}
public string GetLastName() => throw new NotImplementedException();
}