DEV Community

.NET 10 Generic Math Shift Masking: Catch Overshifts That Now Wrap

.NET 10 generic math shift masking changes the result of some oversized shifts on small integer types. If a helper uses IShiftOperators , code such as a generic byte > , and >>> . This is specifically about operators dispatched through generic math. A concrete C# expression involving a byte can be promoted to int ; a generic method constrained by IShiftOperators returns T . That distinction is why a normal happy-path unit test may not reveal the upgrade boundary. Here is the core of the reproducer: static T ShiftLeft (T value, int count) where T : IShiftOperators => value (T value, int count) where T : IShiftOperators => value >>> count; The generic math guide explains the static interface-member model behind these constraints. The syntax did not change here. The built-in implementations did. Reproduce the .NET 9 and .NET 10 boundary The runnable sample multi-targets net9.0 and net10.0 , then runs the same cases under both installed runtimes. It checks counts equal to the type width and one greater than the width. It also includes int > helpers, and methods that accept an unvalidated count. I then prioritize small built-in types. Calls whose count is a compile-time constant below the width are not affected; input-derived counts, sentinels equal to the width, and reusable bit-packers deserve the cross-runtime test. Searching only for byte (T value, int count, int width) where T : IShiftOperators { ArgumentOutOfRangeException.ThrowIfNegative(count); ArgumentOutOfRangeException.ThrowIfNegativeOrZero(width); if (count >= width) throw new ArgumentOutOfRangeException(nameof(count)); return value (T value, int count, int width) where T : IShiftOperators { ArgumentOutOfRangeException.ThrowIfNegative(count); ArgumentOutOfRangeException.ThrowIfNegativeOrZero(width); return value << (count % width); } With the matching built-in value width, both versions behave the same on .NET 9 and .NET 10. More importantly, a reviewer can see the policy without knowing a runtime-specific operator rule. Keep width close to the numeric type rather than accepting an unrelated caller-provided value. In a reusable library, a type-specific helper or a small metadata table is clearer than letting every call site guess. The sample passes the width explicitly so the two policies remain visible, testable, and easy to adapt. When not to use modulo masking Do not add % width simply to preserve the new output. If an oversized count signals corrupt input, masking turns an invalid value into a plausible one. Rejection is usually safer for parsers, authorization bitsets, storage formats, and externally supplied offsets. This sample also does not define semantics for custom numeric types; their operator implementations remain their own contracts. Review rotations, sign extension, negative counts, and cryptographic code separately. A shift verifier can expose a changed result, but it cannot decide whether that result is correct for the domain. .NET 10 is a stable LTS release, and the current 10.0.11 release notes list the SDK/runtime builds used for this check. If your library targets multiple runtimes, I would keep the cross-target assertion until the oldest affected target leaves support. Would your code reject an oversized shift count, or is modulo behavior part of its contract? Happy coding! Top comments (0)

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.