[英]Strongly Typed String
我有一個原型類TypedString<T>
,試圖“強類型”(可疑意義)某個類別的字符串。 它使用了奇怪的重復模板模式(CRTP)的C# 模式 。
class TypedString<T>
public abstract class TypedString<T>
: IComparable<T>
, IEquatable<T>
where T : TypedString<T>
{
public string Value { get; private set; }
protected virtual StringComparison ComparisonType
{
get { return StringComparison.Ordinal; }
}
protected TypedString(string value)
{
if (value == null)
throw new ArgumentNullException("value");
this.Value = Parse(value);
}
//May throw FormatException
protected virtual string Parse(string value)
{
return value;
}
public int CompareTo(T other)
{
return string.Compare(this.Value, other.Value, ComparisonType);
}
public bool Equals(T other)
{
return string.Equals(this.Value, other.Value, ComparisonType);
}
public override bool Equals(object obj)
{
return obj is T && Equals(obj as T);
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
public override string ToString()
{
return Value;
}
}
在整個項目中定義一堆不同的“字符串類別”時,現在可以使用TypedString<T>
類來消除代碼重復。 此類的一個簡單用法示例是定義Username
類:
class Username
(示例) public class Username : TypedString<Username>
{
public Username(string value)
: base(value)
{
}
protected override string Parse(string value)
{
if (!value.Any())
throw new FormatException("Username must contain at least one character.");
if (!value.All(char.IsLetterOrDigit))
throw new FormatException("Username may only contain letters and digits.");
return value;
}
}
現在,我可以在整個項目中使用Username
類,而不必檢查用戶名是否格式正確 - 如果我有一個Username
類型的表達式或變量,則保證它是正確的(或null)。
string GetUserRootDirectory(Username user)
{
if (user == null)
throw new ArgumentNullException("user");
return Path.Combine(UsersDirectory, user.ToString());
}
我不必擔心這里的用戶字符串格式化 - 我已經知道它的類型性質是正確的。
IEnumerable<Username> GetFriends(Username user)
{
//...
}
在這里,調用者只根據類型知道返回的內容。 IEnumerable<string>
需要讀入方法或文檔的詳細信息。 更糟糕的是,如果有人要更改GetFriends
的實現, GetFriends
它引入了一個錯誤並產生無效的用戶名字符串,那么該錯誤可能會無聲地傳播給該方法的調用者並造成各種破壞。 這個類型很好的版本阻止了這一點。
System.Uri
是.NET中一個類的一個例子,它只包含一個具有大量格式約束的字符串和輔助屬性/方法來訪問它的有用部分。 所以這是一種證據表明這種方法並不完全瘋狂。
我想這種事情以前已經完成了。 我已經看到了這種方法的好處,不需要再說服自己了。
我可能會缺少一個缺點嗎?
有沒有辦法可以在以后再次咬我?
我並沒有從根本上反對這種方法(並且知道/使用CRTP的榮譽,這可能非常有用)。 該方法允許元數據包裹在單個值周圍,這可能是一件非常好的事情。 它也是可擴展的; 您可以在不破壞接口的情況下向該類型添加其他數據。
我不喜歡你當前的實現似乎很大程度上依賴於基於異常的流程這一事實。 這可能非常適合某些事情或真正特殊的情況。 但是,如果用戶試圖選擇有效的用戶名,他們可能會在此過程中拋出數十個異常。
當然,您可以向界面添加無異常驗證。 您還必須問自己,您希望驗證規則存在於何處(這始終是一項挑戰,尤其是在分布式應用程序中)。
說到“分發”:考慮將這些類型作為WCF數據合同的一部分實施的含義。 忽略數據契約通常應該暴露簡單DTO的事實,你也有代理類的問題,它將維護你的類型的屬性,而不是它的實現。
當然,您可以通過將父程序集放在客戶端和服務器上來緩解這種情況。 在某些情況下,這是完全合適的。 在其他情況下,不那么重要。 假設您的某個字符串的驗證需要調用數據庫。 這很可能不適合在客戶端/服務器位置。
聽起來你正在尋求一致的格式化。 這是一個有價值的目標,適用於URI和用戶名之類的東西。 對於更復雜的字符串,這可能是一個挑戰。 我已經研究過產品,即使是“簡單”的字符串也可以根據上下文以多種不同的方式進行格式化。 在這種情況下,專用(可能是可重復使用的)格式化程序可能更合適。
同樣,非常具體情況。
更糟糕的是,如果有人要更改GetFriends的實現,以致它引入了一個錯誤並產生無效的用戶名字符串,那么該錯誤可能會無聲地傳播給該方法的調用者並造成各種破壞。
IEnumerable<Username> GetFriends(Username user) { }
我可以看到這個論點。 我想到了一些事情:
GetUserNamesOfFriends()
旁注:在處理人/用戶時,不可變ID可能更有用(人們喜歡更改用戶名)。
System.Uri是.NET中一個類的一個例子,它只包含一個具有大量格式約束的字符串和輔助屬性/方法來訪問它的有用部分。 所以這是一種證據表明這種方法並不完全瘋狂。
沒有爭論,BCL中有很多這樣的例子。
ASP.Net MVC對字符串使用了類似的范例。 如果值為IMvcHtmlString
,則將其視為受信任且不再編碼。 如果沒有,則進行編碼。
以下是我能想到的兩個缺點:
1)維護開發人員可能會感到意外。 他們也可能只是決定使用CLR類型,然后你的代碼被分成使用代碼string username
在一些地方和Username username
在其他國家。
2)調用new Username(str)
和username.Value
可能會使代碼混亂。 現在看起來似乎不太多,但是第20次輸入username.StartsWith("a")
並且必須等待IntelliSense告訴你出現了問題,然后再考慮它然后將其更正為username.Value.StartsWith("a")
你可能會生氣。
我相信你真正想要的是Ada所謂的“受限制的亞型” ,但我自己從未使用過Ada。 在C#中,你能做的最好的是一個包裝器,不太方便。
您已經為可以從字符串中解析的內容的對象表示定義了基類。 使基類中的所有成員都是虛擬的,除了它看起來很好。 您可以考慮稍后管理序列化,區分大小寫等。
這種對象表示在基類庫中使用,例如System.Uri :
Uri uri = new Uri("ftp://myUrl/%2E%2E/%2E%2E");
Console.WriteLine(uri.AbsoluteUri);
Console.WriteLine(uri.PathAndQuery);
使用這個基類,可以很容易地實現對部件的輕松訪問(比如使用System.Uri),強類型成員,驗證等。我看到的唯一缺點是c#中不允許多重繼承,但你可能不需要無論如何繼承任何其他類。
我會推薦另一種設計。
定義描述解析規則的簡單接口(字符串語法):
internal interface IParseRule
{
bool Parse(string input, out string errorMessage);
}
定義用戶名(以及您擁有的其他規則)的解析規則:
internal class UserName : IParseRule
{
public bool Parse(string input, out string errorMessage)
{
// TODO: Do your checks here
if (string.IsNullOrWhiteSpace(input))
{
errorMessage = "User name cannot be empty or consist of white space only.";
return false;
}
else
{
errorMessage = null;
return true;
}
}
}
然后添加一些使用該接口的擴展方法:
internal static class ParseRule
{
public static bool IsValid<TRule>(this string input, bool throwError = false) where TRule : IParseRule, new()
{
string errorMessage;
IParseRule rule = new TRule();
if (rule.Parse(input, out errorMessage))
{
return true;
}
else if (throwError)
{
throw new FormatException(errorMessage);
}
else
{
return false;
}
}
public static void CheckArg<TRule>(this string input, string paramName) where TRule : IParseRule, new()
{
string errorMessage;
IParseRule rule = new TRule();
if (!rule.Parse(input, out errorMessage))
{
throw new ArgumentException(errorMessage, paramName);
}
}
[Conditional("DEBUG")]
public static void DebugAssert<TRule>(this string input) where TRule : IParseRule, new()
{
string errorMessage;
IParseRule rule = new TRule();
Debug.Assert(rule.Parse(input, out errorMessage), "Malformed input: " + errorMessage);
}
}
您現在可以編寫用於驗證字符串語法的干凈代碼:
public void PublicApiMethod(string name)
{
name.CheckArg<UserName>("name");
// TODO: Do stuff...
}
internal void InternalMethod(string name)
{
name.DebugAssert<UserName>();
// TODO: Do stuff...
}
internal bool ValidateInput(string name, string email)
{
return name.IsValid<UserName>() && email.IsValid<Email>();
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.