פתרון חוזי שינוי¶
המבנה הבא שומר invariant גם בבנאי, ומתייחס לחיתוך באורך אפס כאל תוצאה תקפה כאשר הגבולות נוגעים. ערך default הוא הטווח הריק שמתחיל באפס, ולכן גם יצירה ללא קריאת הבנאי אינה שוברת את החוזה.
Console.WriteLine(ByteRange.TryIntersect(new(2, 5), new(7, 3), out var touching));
Console.WriteLine(touching.Length);
Console.WriteLine(ByteRange.TryIntersect(new(2, 3), new(9, 1), out _));
var nearEnd = new ByteRange(long.MaxValue - 2, 2);
Console.WriteLine(nearEnd.End);
try { _ = new ByteRange(long.MaxValue, 1); }
catch (OverflowException) { Console.WriteLine("overflow rejected"); }
readonly struct ByteRange
{
public long Start { get; }
public long Length { get; }
public long End => checked(Start + Length);
public ByteRange(long start, long length)
{
if (start < 0 || length < 0) throw new ArgumentOutOfRangeException();
_ = checked(start + length);
Start = start;
Length = length;
}
public static bool TryIntersect(ByteRange a, ByteRange b, out ByteRange result)
{
long start = Math.Max(a.Start, b.Start);
long end = Math.Min(a.End, b.End);
if (end < start) { result = default; return false; }
result = new ByteRange(start, end - start);
return true;
}
}
השדה readonly מונע שינוי דרך אותו שדה, ולכן קריאה לשיטת struct שיכולה לשנות אותו פועלת על עותק הגנתי. אפשר להפוך את שיטת הקריאה ל־readonly ולקבל את הערך בלי לשנותו. הפונקציה שמשנה את המונה תקבל ref Counter ותיקרא רק ממקום שמורשה לשנות את הערך; הקומפיילר ידחה העברת שדה readonly בתור ref. הפרדה כזאת מאפשרת לזהות שינוי בחתימה במקום להסתמך על תופעת לוואי שמתרחשת בעותק זמני.