2013-04-15 38 views
4

我使用的是結構中的一個項目,像這樣:有沒有更清晰的方式來代表這個成語在C#中?

struct Position 
{ 
    public int X { get; private set; } 
    public int Y { get; private set; } 
    // etc 
} 

我想補充的方法,讓我創造了結構的修改副本隨意更改的屬性。例如,使用它可能會很方便:

var position = new Position(5, 7); 
var newPos = position.With(X: position.X + 1); 

這個習語hacky?有更好的方法來支持嗎?

public Position With(int? X = null, int? Y = null) 
{ 
    return new Position(X ?? this.X, Y ?? this.Y); 
} 

編輯:如果現在還不清楚,該結構是不可改變的,我只是想創建修改一些值的新值。順便提一句,這與Haskell的記錄語法糖非常相似,其中一個會寫newPos = oldPos { x = x oldPos + 1 }。對於這樣一個習語在C#中是否有用,這只是一個實驗。

+0

屬於http://codereview.stackexchange.com – MikroDel

+0

我原以爲創建一個深層克隆的接口會更合適。請注意,不建議使用標準ICloneable,請參閱http://blogs.msdn.com/b/brada/archive/2003/04/09/49935.aspx – chrisw

+2

您是否真的需要結構可變嗎?你肯定需要*一個*方法而不是兩個? –

回答

2

就個人而言,我認爲普通舊數據結構的成語被大大低估。在公共領域以外的其他任何封裝狀態的可變結構都是有問題的,但有時將固定的變量集合在一起並用膠帶粘在一起以便它們可以作爲一個單元傳遞是有用的。一個普通的舊數據結構非常適合這種用法;它表現得像一個固定的變量集合,粘在一起的膠帶,因爲這就是它。人們可以通過一些工作想出一個不可變的類,它需要緩慢且難以閱讀的代碼來處理任何事情,或者通過一些更多的工作來獲得仍然很慢但不太美觀的東西;人們也可以以模仿這樣的類的方式編碼結構。然而,在許多情況下,經歷所有這些努力的唯一影響是,如果人們只是簡單地使用PODS,那麼他的代碼將會變得更慢並且不太清晰。

需要理解的關鍵是像struct PersonInfo { public string Name, SSN; public Date Birthdate; }這樣的PODS 而不是代表一個人。它代表了一個可容納兩個字符串和一個日期的空間。如果有人說var fredSmithInfo = myDatabase.GetPersonInfo("Fred Smith");,那麼FredSmithInfo.BirthDate並不代表弗雷德史密斯的出生日期;它表示一個Date類型的變量,它最初被加載到GetPersonInfo的調用返回的值 - 但與Date類型的任何其他變量一樣,可以更改爲包含任何其他日期。

1

這就像你將要得到的一樣。對我來說似乎並不特別黑。

雖然在那裏你只是在做案例position.X + 1它會是整潔,擁有一樣東西像:

var position = new Position(5,7); 
var newPos = position.Add(new Position(1,0)); 

這會給你一個修改的X值,但不能修改Y值。

+0

似乎是多餘的 - 在這種情況下,他們可能會做'position = new Position(...)'。 –

+0

這是一致的事情,只是保持它與.Net如何做的事情。例如'DateTime'。 – PhonicUK

+2

添加一個*位置*到另一個沒有多大意義。是的,你可以使用原始數據來工作,但它不會使*邏輯*有意義。將矢量添加到位置更有意義。所以雖然「倫敦+紐約」在邏輯上不給你一個位置,「倫敦+10英里東」確實如此。 –

0

可以將此方法視爲prototype pattern的變體,其中重點在於具有模板結構而不是避免新實例的成本。設計的好壞取決於你的環境。如果你可以使語法背後的消息清晰(我認爲你使用的名稱With有點不明確;可能類似CreateVariantCreateMutant會使意圖更清楚),我認爲這是一個合適的方法。

0

我還添加了基於表達式的表單。請注意,由於它是一個結構,所以需要進行可怕的裝箱/取消裝箱。

但正如人們可以看到的格式是相當不錯的:

var p2 = p.With(t => t.X, 4); 
var p3 = p.With(t => t.Y, 7).With(t => t.X, 5); // Yeah, replace all the values :) 

而且方法實在是適用於各種類型。

public void Test() 
{ 
    var p = new Position(8, 3); 

    var p2 = p.With(t => t.X, 4); 
    var p3 = p.With(t => t.Y, 7).With(t => t.X, 5); 

    Console.WriteLine(p); 
    Console.WriteLine(p2); 
    Console.WriteLine(p3); 
} 

public struct Position 
{ 
    public Position(int X, int Y) 
    { 
    this._X = X; this._Y = Y; 
    } 

    private int _X; private int _Y; 
    public int X { get { return _X; } private set { _X = value; } } 
    public int Y { get { return _Y; } private set { _Y = value; } } 

    public Position With<T, P>(Expression<Func<Position, P>> propertyExpression, T value) 
    { 
    // Copy this 
    var copy = (Position)this.MemberwiseClone(); 
    // Get the expression, might be both MemberExpression and UnaryExpression 
    var memExpr = propertyExpression.Body as MemberExpression ?? ((UnaryExpression)propertyExpression.Body).Operand as MemberExpression; 
    if (memExpr == null) 
     throw new Exception("Empty expression!"); 

    // Get the propertyinfo, we need this one to set the value 
    var propInfo = memExpr.Member as PropertyInfo; 
    if (propInfo == null) 
     throw new Exception("Not a valid expression!"); 

    // Set the value via boxing and unboxing (mutable structs are evil :)) 
    object copyObj = copy; 
    propInfo.SetValue(copyObj, value); // Since struct are passed by value we must box it 
    copy = (Position)copyObj; 
    // Return the copy 
    return copy; 
    } 

    public override string ToString() 
    { 
    return string.Format("X:{0,4} Y:{1,4}", this.X, this.Y); 
    } 
} 
相關問題