したがって、string
を継承することはできません。 null許容でないstring
を作成することはできません。しかし、私はこれをやりたいです。クラスが必要です。それ以外の場合はnullになる場合にデフォルト値を返すnStringと呼びましょう。ヌル文字列の数、さらにはヌルオブジェクトを知っている可能性のあるJSONオブジェクトがあります。 nullを返さない文字列を持つ構造体を作成したいと思います。
public struct Struct
{
public nString value;
public nString value2;
}
私はこのようなことをすることができると思います:
public struct Struct
{
public string val { get { return val ?? "N/A"; } set { val = value; } }
public string val2 { get { return val2 ?? "N/A"; } set { val2 = value; } };
}
しかし、それは非常に多くの作業です。これを行う方法はありますか?
もちろん、次のnString
構造体を持つこともできます。
public struct nString
{
public nString(string value)
: this()
{
Value = value ?? "N/A";
}
public string Value
{
get;
private set;
}
public static implicit operator nString(string value)
{
return new nString(value);
}
public static implicit operator string(nString value)
{
return value.Value;
}
}
...
public nString val
{
get;
set;
}
obj.val = null;
string x = obj.val; // <-- x will become "N/A";
これにより、string
との間でキャストが可能になります。内部では、例と同じキャストを実行します。すべてのプロパティに対して入力する必要はありません。しかし、これがアプリケーションの保守性にどのような影響を与えるのだろうか。
NString構造体を完全に機能させるために、オーバーロードを含むすべての文字列メソッドを追加しました。誰かがこの問題に遭遇した場合は、このコードをコピーして貼り付けて、気を付けてください。次にドキュメントを追加します。
/// <summary>
/// Non-nullable string.
/// </summary>
public struct nString
{
public nString(string value)
: this()
{
Value = value ?? "";
}
public nString(char[] value)
{
Value = new string(value) ?? "";
}
public nString(char c, int count)
{
Value = new string(c, count) ?? "";
}
public nString(char[] value, int startIndex, int length)
{
Value = new string(value, startIndex, length) ?? "";
}
public string Value
{
get;
private set;
}
public static implicit operator nString(string value)
{
return new nString(value);
}
public static implicit operator string(nString value)
{
return value.Value ?? "";
}
public int CompareTo(string strB)
{
Value = Value ?? "";
return Value.CompareTo(strB);
}
public bool Contains(string value)
{
Value = Value ?? "";
return Value.Contains(value);
}
public void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
{
Value = Value ?? "";
Value.CopyTo(sourceIndex, destination, destinationIndex, count);
}
public bool EndsWith(string value)
{
Value = Value ?? "";
return Value.EndsWith(value);
}
public bool EndsWith(string value, StringComparison comparisonType)
{
Value = Value ?? "";
return Value.EndsWith(value, comparisonType);
}
public override bool Equals(object obj)
{
Value = Value ?? "";
return Value.Equals(obj);
}
public bool Equals(string value)
{
Value = Value ?? "";
return Value.Equals(value);
}
public bool Equals(string value, StringComparison comparisonType)
{
Value = Value ?? "";
return Value.Equals(value, comparisonType);
}
public override int GetHashCode()
{
Value = Value ?? "";
return Value.GetHashCode();
}
public new Type GetType()
{
return typeof(string);
}
public int IndexOf(char value)
{
Value = Value ?? "";
return Value.IndexOf(value);
}
public int IndexOf(string value)
{
Value = Value ?? "";
return Value.IndexOf(value);
}
public int IndexOf(char value, int startIndex)
{
Value = Value ?? "";
return Value.IndexOf(value, startIndex);
}
public int IndexOf(string value, int startIndex)
{
Value = Value ?? "";
return Value.IndexOf(value, startIndex);
}
public int IndexOf(string value, StringComparison comparisonType)
{
Value = Value ?? "";
return Value.IndexOf(value, comparisonType);
}
public int IndexOf(char value, int startIndex, int count)
{
Value = Value ?? "";
return Value.IndexOf(value, startIndex, count);
}
public int IndexOf(string value, int startIndex, int count)
{
Value = Value ?? "";
return Value.IndexOf(value, startIndex, count);
}
public int IndexOf(string value, int startIndex, StringComparison comparisonType)
{
Value = Value ?? "";
return Value.IndexOf(value, startIndex, comparisonType);
}
public int IndexOf(string value, int startIndex, int count, StringComparison comparisonType)
{
Value = Value ?? "";
return Value.IndexOf(value, startIndex, count, comparisonType);
}
public int IndexOfAny(char[] anyOf)
{
Value = Value ?? "";
return Value.IndexOfAny(anyOf);
}
public int IndexOfAny(char[] anyOf, int startIndex)
{
Value = Value ?? "";
return Value.IndexOfAny(anyOf, startIndex);
}
public int IndexOfAny(char[] anyOf, int startIndex, int count)
{
Value = Value ?? "";
return Value.IndexOfAny(anyOf, startIndex, count);
}
public string Insert(int startIndex, string value)
{
Value = Value ?? "";
return Value.Insert(startIndex, value);
}
public int LastIndexOf(char value)
{
Value = Value ?? "";
return Value.LastIndexOf(value);
}
public int LastIndexOf(string value)
{
Value = Value ?? "";
return Value.LastIndexOf(value);
}
public int LastIndexOf(char value, int startIndex)
{
Value = Value ?? "";
return Value.LastIndexOf(value, startIndex);
}
public int LastIndexOf(string value, int startIndex)
{
Value = Value ?? "";
return Value.LastIndexOf(value, startIndex);
}
public int LastIndexOf(string value, StringComparison comparisonType)
{
Value = Value ?? "";
return Value.LastIndexOf(value, comparisonType);
}
public int LastIndexOf(char value, int startIndex, int count)
{
Value = Value ?? "";
return Value.LastIndexOf(value, startIndex, count);
}
public int LastIndexOf(string value, int startIndex, int count)
{
Value = Value ?? "";
return Value.LastIndexOf(value, startIndex, count);
}
public int LastIndexOf(string value, int startIndex, StringComparison comparisonType)
{
Value = Value ?? "";
return Value.LastIndexOf(value, startIndex, comparisonType);
}
public int LastIndexOf(string value, int startIndex, int count, StringComparison comparisonType)
{
Value = Value ?? "";
return Value.LastIndexOf(value, startIndex, count, comparisonType);
}
public int LastIndexOfAny(char[] anyOf)
{
Value = Value ?? "";
return Value.LastIndexOfAny(anyOf);
}
public int LastIndexOfAny(char[] anyOf, int startIndex)
{
Value = Value ?? "";
return Value.LastIndexOfAny(anyOf, startIndex);
}
public int LastIndexOfAny(char[] anyOf, int startIndex, int count)
{
Value = Value ?? "";
return Value.LastIndexOfAny(anyOf, startIndex, count);
}
public int Length
{
get
{
Value = Value ?? "";
return Value.Length;
}
}
public string PadLeft(int totalWidth)
{
Value = Value ?? "";
return Value.PadLeft(totalWidth);
}
public string PadLeft(int totalWidth, char paddingChar)
{
Value = Value ?? "";
return Value.PadLeft(totalWidth, paddingChar);
}
public string PadRight(int totalWidth)
{
Value = Value ?? "";
return Value.PadRight(totalWidth);
}
public string PadRight(int totalWidth, char paddingChar)
{
Value = Value ?? "";
return Value.PadRight(totalWidth, paddingChar);
}
public string Remove(int startIndex)
{
Value = Value ?? "";
return Value.Remove(startIndex);
}
public string Remove(int startIndex, int count)
{
Value = Value ?? "";
return Value.Remove(startIndex, count);
}
public string Replace(char oldChar, char newChar)
{
Value = Value ?? "";
return Value.Replace(oldChar, newChar);
}
public string Replace(string oldValue, string newValue)
{
Value = Value ?? "";
return Value.Replace(oldValue, newValue);
}
public string[] Split(params char[] separator)
{
Value = Value ?? "";
return Value.Split(separator);
}
public string[] Split(char[] separator, StringSplitOptions options)
{
Value = Value ?? "";
return Value.Split(separator, options);
}
public string[] Split(string[] separator, StringSplitOptions options)
{
Value = Value ?? "";
return Value.Split(separator, options);
}
public bool StartsWith(string value)
{
Value = Value ?? "";
return Value.StartsWith(value);
}
public bool StartsWith(string value, StringComparison comparisonType)
{
Value = Value ?? "";
return Value.StartsWith(value, comparisonType);
}
public string Substring(int startIndex)
{
Value = Value ?? "";
return Value.Substring(startIndex);
}
public string Substring(int startIndex, int length)
{
Value = Value ?? "";
return Value.Substring(startIndex, length);
}
public char[] ToCharArray()
{
Value = Value ?? "";
return Value.ToCharArray();
}
public string ToLower()
{
Value = Value ?? "";
return Value.ToLower();
}
public string ToLowerInvariant()
{
Value = Value ?? "";
return Value.ToLowerInvariant();
}
public override string ToString()
{
Value = Value ?? "";
return Value.ToString();
}
public string ToUpper()
{
Value = Value ?? "";
return Value.ToUpper();
}
public string ToUpperInvariant()
{
Value = Value ?? "";
return Value.ToUpperInvariant();
}
public string Trim()
{
Value = Value ?? "";
return Value.Trim();
}
public string Trim(params char[] trimChars)
{
Value = Value ?? "";
return Value.Trim(trimChars);
}
public string TrimEnd(params char[] trimChars)
{
Value = Value ?? "";
return Value.TrimEnd(trimChars);
}
public string TrimStart(params char[] trimChars)
{
Value = Value ?? "";
return Value.TrimStart(trimChars);
}
}
値型(struct
)を作成して.NETプリミティブ型をラップし、実際のオーバーヘッドを追加せずに型の周りにいくつかのルールを追加できるため、正しい方向に進んでいます。
唯一の問題は、文字列をデフォルトで初期化できるのとまったく同じように、値型をデフォルトで初期化できることです。したがって、「無効」または「空」または「ヌル」の値が存在することを回避することはできません。
これは、文字列をnullまたは空にすることはできないというルールが追加された文字列をラップするクラスです。より良い名前がないため、私はそれをText
と呼ぶことにしました:
struct Text : IEquatable<Text> {
readonly String value;
public Text(String value) {
if (!IsValid(value))
throw new ArgumentException("value");
this.value = value;
}
public static implicit operator Text(String value) {
return new Text(value);
}
public static implicit operator String(Text text) {
return text.value;
}
public static Boolean operator ==(Text a, Text b) {
return a.Equals(b);
}
public static Boolean operator !=(Text a, Text b) {
return !(a == b);
}
public Boolean Equals(Text other) {
return Equals(this.value, other.value);
}
public override Boolean Equals(Object obj) {
if (obj == null || obj.GetType() != typeof(Text))
return false;
return Equals((Text) obj);
}
public override Int32 GetHashCode() {
return this.value != null ? this.value.GetHashCode() : String.Empty.GetHashCode();
}
public override String ToString() {
return this.value != null ? this.value : "N/A";
}
public static Boolean IsValid(String value) {
return !String.IsNullOrEmpty(value);
}
public static readonly Text Empty = new Text();
}
IEquatable<T>
インターフェースを実装する必要はありませんが、とにかくEquals
をオーバーライドする必要があるため、これは素晴らしい追加です。
この型を通常の文字列と交換可能に使用できるように、2つの暗黙的なキャスト演算子を作成することにしました。ただし、暗黙的なキャストは少し微妙な場合があるため、一方または両方を明示的なキャスト演算子に変更することを決定する場合があります。暗黙的なキャストを使用する場合は、このタイプにEquals
を実際に使用するときに、文字列に==
演算子を使用しないように、!=
および==
演算子もオーバーライドする必要があります。
次のようなクラスを使用できます。
var text1 = new Text("Alpha");
Text text2 = "Beta"; // Implicit cast.
var text3 = (Text) "Gamma"; // Explicit cast.
var text4 = new Text(""); // Throws exception.
var s1 = (String) text1; // Explicit cast.
String s2 = text2; // Implicit cast.
ただし、まだ「null」または「空」の値があります。
var empty = new Text();
Console.WriteLine(Equals(text, Text.Empty)); // Prints "True".
Console.WriteLine(Text.Empty); // Prints "N/A".
この概念は、より複雑な「文字列」に簡単に拡張できます。構造を持つ電話番号またはその他の文字列。これにより、理解しやすいコードを書くことができます。例:代わりに
public void AddCustomer(String name, String phone) { ... }
あなたはそれをに変更することができます
public void AddCustomer(String name, PhoneNumber phone) { ... }
2番目の関数は、電話番号がすでに有効である必要があるPhoneNumber
であるため、電話番号を検証する必要はありません。それを任意のコンテンツを持つことができる文字列と比較し、各呼び出しでそれを検証する必要があります。ほとんどのベテラン開発者は、社会保障番号、電話番号、国コード、通貨などの値のような文字列に文字列を使用することは悪い習慣であることにおそらく同意するでしょうが、それは非常に一般的なアプローチのようです。
このアプローチには、ヒープ割り当てに関してオーバーヘッドがないことに注意してください。これは、いくつかの追加の検証コードを含む単なる文字列です。
拡張メソッド のようなものを使用できます
public static class StringExtensions
{
public static string GetValueOrNotAvailable(this string value)
{
return value ?? "N/A";
}
}
それならあなたはそれをこのように呼ぶことができるでしょう
string s = (some variable that could be null)
Console.WriteLine(s.GetValueOrNotAvailable());
残念ながら、文字列のgetメソッドをオーバーライドすることはできません。上記のように、内部文字列を追跡する新しい型を作成できます。
「不変」(*)struct
を定義することは可能です。これは、String
とほぼ同じように動作しますが、デフォルト値はnull
ではなく空の文字列のように動作します。このようなタイプは、タイプString
またはObject
の単一のフィールドをカプセル化し、String
からのナローイング変換を定義して、指定された文字列がnullでないことを確認し、その文字列に格納する必要があります。データフィールド、およびフィールドがnullの場合は空の文字列を返すString
への拡張変換、それ以外の場合はそのToString()
値。 String
のパブリックメンバーごとに、タイプは_(String)this
_の対応するメンバーを呼び出すメンバーを定義する必要があります。このようなタイプは、文字列連結演算子のオーバーロードも定義する必要があります。
(*)_struct1 = struct2;
_は、すべてのパブリックフィールドとプライベートフィールドを対応するフィールドの内容で上書きすることにより、struct1に格納されているインスタンスを変更するため、デフォルトとは明らかに異なる値を保持できるすべての値タイプは変更可能です。 type2であり、構造型がそれを防ぐためにできることは何もありません。
ほとんどの場合、そのような型には単にString
への参照を保持させたいと思うでしょうが、そうでない場合に役立つ場合もあります。たとえば、複数の文字列を保持する1つ以上の不変の「CompositeString」クラスを定義し、それらを連結して結果をキャッシュするToString
メソッドを持つことができます。このようなタイプを使用すると、次のようなループを作成できます。
_for (i=0; i<100000; i++)
st = st + something;
_
観測可能な可変クラスのセマンティクスを使用しなくても、ほぼStringBuilder
の桁内のパフォーマンスが得られます(ループを繰り返すたびに、新しいCompositeString
オブジェクトが生成されますが、多くの情報はオブジェクト間で共有できます)。
最初はString
以外のものをデータフィールドに格納しなかったとしても、Object
を使用してToString()
を呼び出すと、必要に応じて他の実装が可能になります。 。
いいえ、これはできません。
Null許容型を作成する唯一の方法は、 struct
--structsを宣言することです。ただし、構造体は継承または継承できません。
プロパティをそのまま使用するのが最善の方法であるか、前述のように逆シリアル化中にnull合体するのが最も可能性が高いですが、C#は単にnull
値を処理するように設計されています。
2019年4月のC#8のリリースと null許容参照型 で、これは言語機能になりました。