開發時一定會遇到函式傳回結果包含多種資料的情境。舉個例子,假設有個圖檔分析函式,傳入圖檔路徑可得到格式、寬度、高度、色彩深度... 等資訊,單純用字串、整數當成回傳型別一定無法滿足需求。

若傳回資料種類複雜或函式要供外界或其他系統呼叫,那麼設計專屬資料型別當成函式傳回型別是較好選擇,也沒什麼好猶豫。但有很多情況函式只供系統內部呼叫,只在小範圍使用,屬本地函式性質,為此額外宣告結果型別不太符合效益,這篇文章會整理針對這些區域應用性質函式傳回多種資料的做法選擇。

我用一個沒營養(無實用性,示範而已)的求平均、最小值、最大值的計算函式當範例,看看在 C# 有哪些一次接收三種資料的做法:

使用 out 參數

在函式參數加上 avg, min, max 三個 out 參數。小缺點是呼叫前需先宣告 double avg, min, max。但 C# 7 加入了 Out Inlined Variable Declaration,可省下事先宣告的麻煩,新一代的 out 變數用起來更簡潔。

double[] Data = new double[] { 1, 2, 3, 4 };

public void CalcStats1(IEnumerable<double> numbers,
        out double avg, out double min, out double max
    )
{
    avg = numbers.Average();
    min = numbers.Min();
    max = numbers.Max();
}
//C# 7.0 以前
public void TestOutParamOld() 
{
    double avg, min, max;
    CalcStats1(Data, out avg, out min, out max);
    Console.WriteLine($"Avg: {avg} / Min: {min} / Max: {max}");
}
//C# 7.0 之後, .NET Framework 4.7+, .NET Core 2.0+
public void TestOutParamBetter() 
{
    CalcStats1(Data, out var avg, out var min, out var max);
    Console.WriteLine($"Avg: {avg} / Min: {min} / Max: {max}");
}

使用專屬結果型別

特別宣告一個包含 Avg、Min、Max 的型別(範例中的 CalcStatsResult)用來傳結果,語意邏輯最明確,但若函式只在組件內部使用又只用一次,為此生出一個型別有點小題大做。

public class CalcStatsResult 
{
	public double Avg { get; set; }
	public double Min { get; set; }
	public double Max { get; set; }
}

public CalcStatsResult CalcStats2(IEnumerable<double> numbers)
{
    return new CalcStatsResult {
		Avg = numbers.Average(),
    	Min = numbers.Min(),
    	Max= numbers.Max()
	};
}

public void TestResType() 
{
	var res = CalcStats2(Data);
	Console.WriteLine($"Avg: {res.Avg} / Min: {res.Min} / Max: {res.Max}");
}

使用 ValueTuple

C# 7.0 推出的 ValueTuple,可為元素自訂名稱,用起來很接近自訂類別,不需宣告就能享受強型別的好處。甚至可以用 (var avg, var min, var max) = FuncToReturnTuple() 將 Tuple 解構成多個變數,數變型別請 Compiler 推斷,簡潔到了極點,是我心中最好的新時代寫法。

public (double Avg, double Min, double Max) CalcStats3(IEnumerable<double> numbers)
{
    return (numbers.Average(), numbers.Min(), numbers.Max());
}

public void TestTuple() 
{
	var res = CalcStats3(Data);
	Console.WriteLine($"Avg: {res.Avg} / Min: {res.Min} / Max: {res.Max}");
	// 更方便的寫法,直接解構成三個變數,變數型別請 Compiler 推斷
	var (avg, min, max) = CalcStats3(Data);
	Console.WriteLine($"Avg: {avg} / Min: {min} / Max: {max}");
}

其他(不入流)做法

我還曾用過 Dictionary<string, object>、轉成 CSV 字串再拆解... 等做法,但把 C# 搞得像 JavaScript,強型別優勢消失殆盡,現在想想有點不入流,就不列入參考了。

以上是我想到幾種 C# 函式回傳多元資料的做法,歡迎大家補充。

This article introduce simple ways to return complex results from .NET functions.


Comments

# by eric

乖乖宣告類別物件化,或使用dynamic? public static dynamic getResult(List<double> data){ return new { Max = data.Max(), Min = data.Min(), Avg = data.Average() };

# by Jeffrey

to eric, 用 dynamic 的話無法享用強型別的好處,例如呼叫端 Avg 打成 Avq (很像我會做的事 :D) 要等到執行時才會發現。

# by ChrisTorng

黑大忘記 record 了? (還有 record class/record struct 可選) public record struct CalcStatsResult(double Avg, double Min, double Max);

Post a comment