11using System ;
22using System . Collections . Generic ;
3+ using System . Globalization ;
4+ using System . Linq ;
5+ using System . Text ;
36
47namespace MyNumberNET
58{
@@ -103,4 +106,299 @@ public MyNumberMalformedException(string message)
103106
104107 #endregion
105108 }
109+
110+ /// <summary>
111+ /// Represents a valid My Number that enforces correct format and provides type safety.
112+ /// This is an immutable value type that ensures the My Number is always in a valid state.
113+ /// </summary>
114+ public readonly struct MyNumberValue : IEquatable < MyNumberValue > , IFormattable
115+ {
116+ private readonly int [ ] _digits ;
117+
118+ /// <summary>
119+ /// Gets the 12-digit array representation of this My Number.
120+ /// </summary>
121+ public int [ ] Digits => ( int [ ] ) _digits ? . Clone ( ) ?? throw new InvalidOperationException ( "MyNumberValue is not initialized." ) ;
122+
123+ /// <summary>
124+ /// Gets whether this MyNumberValue instance has been properly initialized.
125+ /// </summary>
126+ public bool IsInitialized => _digits != null ;
127+
128+ /// <summary>
129+ /// Initializes a new instance of MyNumberValue from a 12-digit array.
130+ /// </summary>
131+ /// <param name="digits">A 12-digit array representing a valid My Number.</param>
132+ /// <exception cref="MyNumber.MyNumberMalformedException">Thrown when the digits don't represent a valid My Number.</exception>
133+ public MyNumberValue ( int [ ] digits )
134+ {
135+ if ( ! MyNumber . VerifyNumber ( digits ) )
136+ {
137+ throw new MyNumber . MyNumberMalformedException ( "The provided digits do not represent a valid My Number." ) ;
138+ }
139+ _digits = ( int [ ] ) digits . Clone ( ) ;
140+ }
141+
142+ /// <summary>
143+ /// Initializes a new instance of MyNumberValue from individual digit parameters.
144+ /// </summary>
145+ /// <param name="d1">First digit</param>
146+ /// <param name="d2">Second digit</param>
147+ /// <param name="d3">Third digit</param>
148+ /// <param name="d4">Fourth digit</param>
149+ /// <param name="d5">Fifth digit</param>
150+ /// <param name="d6">Sixth digit</param>
151+ /// <param name="d7">Seventh digit</param>
152+ /// <param name="d8">Eighth digit</param>
153+ /// <param name="d9">Ninth digit</param>
154+ /// <param name="d10">Tenth digit</param>
155+ /// <param name="d11">Eleventh digit</param>
156+ /// <param name="d12">Twelfth digit (check digit)</param>
157+ /// <exception cref="MyNumber.MyNumberMalformedException">Thrown when the digits don't represent a valid My Number.</exception>
158+ public MyNumberValue ( int d1 , int d2 , int d3 , int d4 , int d5 , int d6 , int d7 , int d8 , int d9 , int d10 , int d11 , int d12 )
159+ : this ( new [ ] { d1 , d2 , d3 , d4 , d5 , d6 , d7 , d8 , d9 , d10 , d11 , d12 } )
160+ {
161+ }
162+
163+ /// <summary>
164+ /// Creates a MyNumberValue from the first 11 digits, automatically calculating the check digit.
165+ /// </summary>
166+ /// <param name="firstElevenDigits">The first 11 digits of the My Number.</param>
167+ /// <returns>A valid MyNumberValue with the calculated check digit.</returns>
168+ /// <exception cref="MyNumber.MyNumberMalformedException">Thrown when the input is invalid.</exception>
169+ public static MyNumberValue FromFirstElevenDigits ( int [ ] firstElevenDigits )
170+ {
171+ if ( firstElevenDigits == null || firstElevenDigits . Length != 11 )
172+ {
173+ throw new MyNumber . MyNumberMalformedException ( "Must provide exactly 11 digits." ) ;
174+ }
175+
176+ var checkDigit = MyNumber . CalculateCheckDigits ( firstElevenDigits ) ;
177+ var allDigits = new int [ 12 ] ;
178+ Array . Copy ( firstElevenDigits , allDigits , 11 ) ;
179+ allDigits [ 11 ] = checkDigit ;
180+
181+ return new MyNumberValue ( allDigits ) ;
182+ }
183+
184+ /// <summary>
185+ /// Attempts to parse a string representation of a My Number.
186+ /// </summary>
187+ /// <param name="value">String containing 12 digits (may include separators like spaces or hyphens).</param>
188+ /// <param name="result">The parsed MyNumberValue if successful.</param>
189+ /// <returns>True if parsing was successful, false otherwise.</returns>
190+ public static bool TryParse ( string value , out MyNumberValue result )
191+ {
192+ result = default ;
193+
194+ if ( string . IsNullOrWhiteSpace ( value ) )
195+ return false ;
196+
197+ // Remove common separators
198+ var cleanValue = value . Replace ( " " , "" ) . Replace ( "-" , "" ) . Replace ( "_" , "" ) ;
199+
200+ if ( cleanValue . Length != 12 )
201+ return false ;
202+
203+ var digits = new int [ 12 ] ;
204+ for ( int i = 0 ; i < 12 ; i ++ )
205+ {
206+ if ( ! char . IsDigit ( cleanValue [ i ] ) )
207+ return false ;
208+ digits [ i ] = cleanValue [ i ] - '0' ;
209+ }
210+
211+ try
212+ {
213+ result = new MyNumberValue ( digits ) ;
214+ return true ;
215+ }
216+ catch ( MyNumber . MyNumberMalformedException )
217+ {
218+ return false ;
219+ }
220+ }
221+
222+ /// <summary>
223+ /// Parses a string representation of a My Number.
224+ /// </summary>
225+ /// <param name="value">String containing 12 digits (may include separators like spaces or hyphens).</param>
226+ /// <returns>A valid MyNumberValue.</returns>
227+ /// <exception cref="ArgumentException">Thrown when the string cannot be parsed as a valid My Number.</exception>
228+ public static MyNumberValue Parse ( string value )
229+ {
230+ if ( TryParse ( value , out var result ) )
231+ return result ;
232+
233+ throw new ArgumentException ( $ "Unable to parse '{ value } ' as a valid My Number.", nameof ( value ) ) ;
234+ }
235+
236+ /// <summary>
237+ /// Generates a random valid My Number.
238+ /// </summary>
239+ /// <returns>A randomly generated valid MyNumberValue.</returns>
240+ public static MyNumberValue GenerateRandom ( )
241+ {
242+ var generator = new MyNumber ( ) ;
243+ var digits = generator . GenerateRandomNumber ( ) ;
244+ return new MyNumberValue ( digits ) ;
245+ }
246+
247+ /// <summary>
248+ /// Returns the string representation of this My Number.
249+ /// </summary>
250+ /// <returns>A 12-digit string representation.</returns>
251+ public override string ToString ( )
252+ {
253+ return ToString ( "N" , CultureInfo . InvariantCulture ) ;
254+ }
255+
256+ /// <summary>
257+ /// Returns the string representation of this My Number with the specified format.
258+ /// </summary>
259+ /// <param name="format">
260+ /// Format string:
261+ /// "N" or null = no separators (default): "123456789012"
262+ /// "S" = with spaces: "1234 5678 9012"
263+ /// "H" = with hyphens: "1234-5678-9012"
264+ /// "G" = grouped format: "1234-5678-901-2"
265+ /// </param>
266+ /// <returns>Formatted string representation.</returns>
267+ public string ToString ( string format )
268+ {
269+ return ToString ( format , CultureInfo . InvariantCulture ) ;
270+ }
271+
272+ /// <summary>
273+ /// Returns the string representation of this My Number with the specified format and format provider.
274+ /// </summary>
275+ /// <param name="format">Format string (see ToString(string) for options).</param>
276+ /// <param name="formatProvider">Format provider (currently not used).</param>
277+ /// <returns>Formatted string representation.</returns>
278+ public string ToString ( string format , IFormatProvider formatProvider )
279+ {
280+ if ( ! IsInitialized )
281+ throw new InvalidOperationException ( "MyNumberValue is not initialized." ) ;
282+
283+ var digitString = string . Join ( "" , _digits ) ;
284+
285+ return format ? . ToUpperInvariant ( ) switch
286+ {
287+ null or "N" => digitString ,
288+ "S" => $ "{ digitString . Substring ( 0 , 4 ) } { digitString . Substring ( 4 , 4 ) } { digitString . Substring ( 8 , 4 ) } ",
289+ "H" => $ "{ digitString . Substring ( 0 , 4 ) } -{ digitString . Substring ( 4 , 4 ) } -{ digitString . Substring ( 8 , 4 ) } ",
290+ "G" => $ "{ digitString . Substring ( 0 , 4 ) } -{ digitString . Substring ( 4 , 4 ) } -{ digitString . Substring ( 8 , 3 ) } -{ digitString . Substring ( 11 , 1 ) } ",
291+ _ => throw new FormatException ( $ "Format string '{ format } ' is not supported.")
292+ } ;
293+ }
294+
295+ /// <summary>
296+ /// Determines whether this instance is equal to another MyNumberValue.
297+ /// </summary>
298+ /// <param name="other">The other MyNumberValue to compare.</param>
299+ /// <returns>True if equal, false otherwise.</returns>
300+ public bool Equals ( MyNumberValue other )
301+ {
302+ if ( ! IsInitialized && ! other . IsInitialized )
303+ return true ;
304+ if ( ! IsInitialized || ! other . IsInitialized )
305+ return false ;
306+
307+ return _digits . SequenceEqual ( other . _digits ) ;
308+ }
309+
310+ /// <summary>
311+ /// Determines whether this instance is equal to the specified object.
312+ /// </summary>
313+ /// <param name="obj">The object to compare.</param>
314+ /// <returns>True if equal, false otherwise.</returns>
315+ public override bool Equals ( object obj )
316+ {
317+ return obj is MyNumberValue other && Equals ( other ) ;
318+ }
319+
320+ /// <summary>
321+ /// Gets the hash code for this MyNumberValue.
322+ /// </summary>
323+ /// <returns>A hash code for this instance.</returns>
324+ public override int GetHashCode ( )
325+ {
326+ if ( ! IsInitialized )
327+ return 0 ;
328+
329+ unchecked
330+ {
331+ int hash = 17 ;
332+ foreach ( var digit in _digits )
333+ {
334+ hash = hash * 31 + digit ;
335+ }
336+ return hash ;
337+ }
338+ }
339+
340+ /// <summary>
341+ /// Determines whether two MyNumberValue instances are equal.
342+ /// </summary>
343+ /// <param name="left">The first MyNumberValue.</param>
344+ /// <param name="right">The second MyNumberValue.</param>
345+ /// <returns>True if equal, false otherwise.</returns>
346+ public static bool operator == ( MyNumberValue left , MyNumberValue right )
347+ {
348+ return left . Equals ( right ) ;
349+ }
350+
351+ /// <summary>
352+ /// Determines whether two MyNumberValue instances are not equal.
353+ /// </summary>
354+ /// <param name="left">The first MyNumberValue.</param>
355+ /// <param name="right">The second MyNumberValue.</param>
356+ /// <returns>True if not equal, false otherwise.</returns>
357+ public static bool operator != ( MyNumberValue left , MyNumberValue right )
358+ {
359+ return ! left . Equals ( right ) ;
360+ }
361+
362+ /// <summary>
363+ /// Implicitly converts a MyNumberValue to an int array.
364+ /// </summary>
365+ /// <param name="myNumber">The MyNumberValue to convert.</param>
366+ /// <returns>A 12-digit int array.</returns>
367+ public static implicit operator int [ ] ( MyNumberValue myNumber )
368+ {
369+ return myNumber . Digits ;
370+ }
371+
372+ /// <summary>
373+ /// Implicitly converts a MyNumberValue to a string.
374+ /// </summary>
375+ /// <param name="myNumber">The MyNumberValue to convert.</param>
376+ /// <returns>A 12-digit string representation.</returns>
377+ public static implicit operator string ( MyNumberValue myNumber )
378+ {
379+ return myNumber . ToString ( ) ;
380+ }
381+
382+ /// <summary>
383+ /// Explicitly converts an int array to a MyNumberValue.
384+ /// </summary>
385+ /// <param name="digits">The 12-digit array to convert.</param>
386+ /// <returns>A MyNumberValue instance.</returns>
387+ /// <exception cref="MyNumber.MyNumberMalformedException">Thrown when the array is not a valid My Number.</exception>
388+ public static explicit operator MyNumberValue ( int [ ] digits )
389+ {
390+ return new MyNumberValue ( digits ) ;
391+ }
392+
393+ /// <summary>
394+ /// Explicitly converts a string to a MyNumberValue.
395+ /// </summary>
396+ /// <param name="value">The string to convert.</param>
397+ /// <returns>A MyNumberValue instance.</returns>
398+ /// <exception cref="ArgumentException">Thrown when the string is not a valid My Number.</exception>
399+ public static explicit operator MyNumberValue ( string value )
400+ {
401+ return Parse ( value ) ;
402+ }
403+ }
106404}
0 commit comments