Results 1 to 7 of 7

Thread: Useful Extension Methods

  1. #1

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Useful Extension Methods

    VB version here.

    This is a library of extension methods I've been building to make various things easier and or cleaner in code. The solution was created in VS 2017 and all initial projects target .NET 4.7.1. The solution contains C# and VB projects and I will provide some extra explanation and examples of the C# code here. The TestBed projects are WinForms and are intended to provide tests for pretty much all the included extensions. They are currently incomplete but I will replace the attachment when I have finished them.

    You can build the solution and use the compiled libraries with your own applications, or I'm happy for anyone to copy whichever parts of the code they find useful and use them directly in their own projects. While I don't require explicit credit, I also require that you don't try to take explicit credit for the code.

    EDIT 23 April, 2018: Improved TestBed projects. Removed ParamArray from 'weightings' parameters of existing RandomExtensions.NextWithWeighting overloads. Added new overloads of RandomExtensions.NextWithWeighting enabling implicitly specifying weightings for some possible outcomes.

    EDIT 23 April, 2018: Improved TestBed projects. Fixed bug in Random project. Fixed bug in VB Numerics project.

    EDIT 3 May, 2018: Changed Image project to Drawing and added DrawCircle and DrawTriangle extensions for Graphics class. Add ToLines method to String extensions.

    EDIT 19 May, 2018: Added ArrayExtensions to Enumerable project. Added unit test projects.
    Attached Files Attached Files

  2. #2

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Useful Extension Methods for Images

    csharp Code:
    1. // Saves an <see cref="Image"/> to an array of <see cref="byte"/>'s.
    2. public static byte[] ToBytes(this Image source)
    3. // Saves an <see cref="Image"/> to an array of <see cref="byte"/>'s in the specified format.
    4. public static byte[] ToBytes(this Image source, ImageFormat format)
    5. // Saves an <see cref="Image"/> to an array of <see cref="byte"/>'s, with the specified encoder and image-encoder parameters.
    6. public static byte[] ToBytes(this Image source, ImageCodecInfo encoder, EncoderParameters encoderParams)
    csharp Code:
    1. // Converts an array of <see cref="byte"/> containing image data into an <see cref="Image"/> object.
    2. public static Image ToImage(this IEnumerable<byte> source)
    The methods in the Image project are basically about converting between an Image object and a Byte array and vice versa. These operations are required when saving and loading images in databases, as well as in other situations.
    csharp Code:
    1. using System.Drawing;
    2. using System.Drawing.Imaging;
    3. using System.IO;
    4.  
    5. namespace Wunnell.Extensions
    6. {
    7.     /// <summary>
    8.     /// Contains methods that extend the <see cref="Image"/> class.
    9.     /// </summary>
    10.     public static class ImageExtensions
    11.     {
    12.         /// <summary>
    13.         /// Saves an <see cref="Image"/> to an array of <see cref="byte"/>'s.
    14.         /// </summary>
    15.         /// <param name="source">
    16.         /// The inoput image.
    17.         /// </param>
    18.         /// <returns>
    19.         /// An array containing the image data.
    20.         /// </returns>
    21.         public static byte[] ToBytes(this Image source)
    22.         {
    23.             return source.ToBytes(source.RawFormat);
    24.         }
    25.  
    26.         /// <summary>
    27.         /// Saves an <see cref="Image"/> to an array of <see cref="byte"/>'s in the specified format.
    28.         /// </summary>
    29.         /// <param name="source">
    30.         /// The inoput image.
    31.         /// </param>
    32.         /// <param name="format">
    33.         /// The <see cref="ImageFormat"/> for this <see cref="Image"/>.
    34.         /// </param>
    35.         /// <returns>
    36.         /// An array containing the image data.
    37.         /// </returns>
    38.         public static byte[] ToBytes(this Image source, ImageFormat format)
    39.         {
    40.             using (var stream = new MemoryStream())
    41.             {
    42.                 source.Save(stream, format);
    43.  
    44.                 return stream.ToArray();
    45.             }
    46.         }
    47.  
    48.         /// <summary>
    49.         /// Saves an <see cref="Image"/> to an array of <see cref="byte"/>'s, with the specified encoder and image-encoder parameters.
    50.         /// </summary>
    51.         /// <param name="source">
    52.         /// The inoput image.
    53.         /// </param>
    54.         /// <param name="encoder"></param>
    55.         /// <param name="encoderParams"></param>
    56.         /// <returns>
    57.         /// An array containing the image data.
    58.         /// </returns>
    59.         public static byte[] ToBytes(this Image source, ImageCodecInfo encoder, EncoderParameters encoderParams)
    60.         {
    61.             using (var stream = new MemoryStream())
    62.             {
    63.                 source.Save(stream, encoder, encoderParams);
    64.  
    65.                 return stream.ToArray();
    66.             }
    67.         }
    68.     }
    69. }
    csharp Code:
    1. using System.Collections.Generic;
    2. using System.Drawing;
    3. using System.IO;
    4. using System.Linq;
    5.  
    6. namespace Wunnell.Extensions
    7. {
    8.     /// <summary>
    9.     /// Contains methods that extend the <see cref="IEnumerable{T}"/> interface.
    10.     /// </summary>
    11.     public static class EnumerableExtensions
    12.     {
    13.         /// <summary>
    14.         /// Converts an array of <see cref="byte"/> containing image data into an <see cref="Image"/> object.
    15.         /// </summary>
    16.         /// <param name="source">
    17.         /// The input byte array.
    18.         /// </param>
    19.         /// <returns>
    20.         /// The image represented by the input data.
    21.         /// </returns>
    22.         public static Image ToImage(this IEnumerable<byte> source)
    23.         {
    24.             using (var stream = new MemoryStream(source.ToArray()))
    25.             {
    26.                 return Image.FromStream(stream);
    27.             }
    28.         }
    29.     }
    30. }
    These methods simply encapsulate the use of a MemoryStream go between an Image and a Byte array or vice versa. Saving an Image to a database might look like this:
    Code:
    using (var connection = new SqlConnection("connection string here"))
    using (var command = new SqlCommand("UPDATE MyTable SET Picture = @Picture WHERE Id = @Id", connection))
    {
        command.Parameters.Add("@Picture", SqlDbType.VarBinary, -1).Value = pictureBox1.Image.ToBytes();
        command.Parameters.Add("@Id", SqlDbType.Int).Value = 1;
    
        connection.Open();
        command.ExecuteNonQuery();
    }
    while retrieving that Image might look like this:
    Code:
    using (var connection = new SqlConnection("connection string here"))
    using (var command = new SqlCommand("SELECT Picture FROM MyTable WHERE Id = @Id", connection))
    {
        command.Parameters.Add("@Id", SqlDbType.Int).Value = 1;
    
        connection.Open();
    
        var data = (byte[]) command.ExecuteScalar();
    
        pictureBox1.Image = data.ToImage();
    }
    The three overloads of ToBytes correlate to the three overloads of Image.Save that have a first parameter of type String, where one saves using the raw format of the Image object, one takes a format to save in and the third takes an encoder and encoding parameters.

  3. #3

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Useful Extension Methods for Random Numbers

    csharp Code:
    1. // Returns a non-negative random integer that is less than the specified maximum where each value in that range has a weighted probability.
    2. public static int NextWithWeighting(this Random source,
    3.                                     int maxValue,
    4.                                     int[] weightings)
    5. // Returns a random integer that is within a specified range where each value in that range has a weighted probability.
    6. public static int NextWithWeighting(this Random source,
    7.                                     int minValue,
    8.                                     int maxValue,
    9.                                     int[] weightings)
    10. // Returns a non-negative random integer that is less than the specified maximum where each value in that range has a weighted probability.
    11. public static int NextWithWeighting(this Random source,
    12.                                     int maxValue,
    13.                                     IDictionary<int,int> weightings)
    14. // Returns a random integer that is within a specified range where each value in that range has a weighted probability.
    15. public static int NextWithWeighting(this Random source,
    16.                                     int minValue,
    17.                                     int maxValue,
    18.                                     IDictionary<int, int> weightings)
    19. // Returns a non-negative random integer that is less than the specified maximum where each value in that range has a weighted probability.
    20. public static int NextWithWeighting(this Random source,
    21.                                     int maxValue,
    22.                                     IDictionary<int, int> weightings,
    23.                                     int defaultWeighting)
    24. // Returns a random integer that is within a specified range where each value in that range has a weighted probability.
    25. public static int NextWithWeighting(this Random source,
    26.                                     int minValue,
    27.                                     int maxValue,
    28.                                     IDictionary<int, int> weightings,
    29.                                     int defaultWeighting)
    The methods in the Random project enable you to generate a random number in a range where each number can have a different likelihood of being generated.
    csharp Code:
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4.  
    5. namespace Wunnell.Extensions
    6. {
    7.     /// <summary>
    8.     /// Contains methods that extend the <see cref="Random"/> class.
    9.     /// </summary>
    10.     public static class RandomExtensions
    11.     {
    12.         private const int DEFAULT_WEIGHTING = 1;
    13.  
    14.         /// <summary>
    15.         /// Returns a non-negative random integer that is less than the specified maximum where each value in that range has a weighted probability.
    16.         /// </summary>
    17.         /// <param name="source">
    18.         /// The <see cref="Random"/> object to use to generate the number.
    19.         /// </param>
    20.         /// <param name="maxValue">
    21.         /// The exclusive upper bound of the random number to be generated. maxValue must be greater than or equal to 0.
    22.         /// </param>
    23.         /// <param name="weightings">
    24.         /// The weightings for each of the possible outcomes.
    25.         /// </param>
    26.         /// <returns>
    27.         /// A 32-bit signed integer that is greater than or equal to 0, and less than maxValue; that is, the range of return values ordinarily includes 0 but not maxValue. However, if maxValue equals 0, maxValue is returned.
    28.         /// </returns>
    29.         /// <remarks>
    30.         /// A non-negative weighting must be provided for each possible outcome.  Weightings are a proportion of the total of all weightings.  They are not percentages.
    31.         /// For instance, if there are three possible outcomes and the weightings are 1, 2 and 3 then the first outcome will result about 1/6 of the time, the second outcome will result about 1/3 of the time and the third outcome will result about 1/2 of the time.
    32.         /// </remarks>
    33.         public static int NextWithWeighting(this Random source,
    34.                                             int maxValue,
    35.                                             int[] weightings)
    36.         {
    37.             return source.NextWithWeighting(0, maxValue, weightings);
    38.         }
    39.  
    40.         /// <summary>
    41.         /// Returns a random integer that is within a specified range where each value in that range has a weighted probability.
    42.         /// </summary>
    43.         /// <param name="source">
    44.         /// The <see cref="Random"/> object to use to generate the number.
    45.         /// </param>
    46.         /// <param name="minValue">
    47.         /// The inclusive lower bound of the random number returned.
    48.         /// </param>
    49.         /// <param name="maxValue">
    50.         /// The exclusive upper bound of the random number returned. maxValue must be greater than or equal to minValue.
    51.         /// </param>
    52.         /// <param name="weightings">
    53.         /// The weightings for each of the possible outcomes.
    54.         /// </param>
    55.         /// <returns>
    56.         /// A 32-bit signed integer greater than or equal to minValue and less than maxValue; that is, the range of return values includes minValue but not maxValue. If minValue equals maxValue, minValue is returned.
    57.         /// </returns>
    58.         /// <remarks>
    59.         /// A non-negative weighting must be provided for each possible outcome.  Weightings are a proportion of the total of all weightings.  They are not percentages.
    60.         /// For instance, if there are three possible outcomes and the weightings are 1, 2 and 3 then the first outcome will result about 1/6 of the time, the second outcome will result about 1/3 of the time and the third outcome will result about 1/2 of the time.
    61.         /// </remarks>
    62.         public static int NextWithWeighting(this Random source,
    63.                                             int minValue,
    64.                                             int maxValue,
    65.                                             int[] weightings)
    66.         {
    67.             if (minValue > maxValue)
    68.             {
    69.                 throw new ArgumentOutOfRangeException($"'{nameof(minValue)}' cannot be greater than {nameof(maxValue)}.", nameof(minValue));
    70.             }
    71.  
    72.             if (maxValue > minValue && weightings.Length != maxValue - minValue)
    73.             {
    74.                 throw new ArgumentException("A weighting must be provided for all possible outcomes.", nameof(weightings));
    75.             }
    76.  
    77.             if (weightings.Any(n => n < 0))
    78.             {
    79.                 throw new ArgumentException("All weightings must be greater than zero.", nameof(weightings));
    80.             }
    81.  
    82.             int totalWeightings;
    83.  
    84.             try
    85.             {
    86.                 totalWeightings = weightings.Sum();
    87.             }
    88.             catch (OverflowException e)
    89.             {
    90.                 throw new ArgumentException("The sum of all weightings must not be greater than Int32.MaxValue.", e);
    91.             }
    92.  
    93.             if (totalWeightings == 0)
    94.             {
    95.                 throw new ArgumentException("The sum of all weightings must be greater than zero.", nameof(weightings));
    96.             }
    97.  
    98.             if (minValue == maxValue || minValue == maxValue + 1)
    99.             {
    100.                 // There is only one possible value.
    101.                 return minValue;
    102.             }
    103.  
    104.             // Generate a number in the range 0 to 1 less than the total weightings.
    105.             var number = source.Next(totalWeightings);
    106.  
    107.             var runningWeighting = 0;
    108.  
    109.             // For each weighting, check whether the number generated falls in that interval.
    110.             for (var i = 0; i < weightings.Length; i++)
    111.             {
    112.                 // Sum the weightings so far.
    113.                 // E.g. if the weightings are 10, 20, 30 and 40 then the running weighting for each iteration will be:
    114.                 // i = 0: runningWeighting = 0 + 10 = 10
    115.                 // i = 1: runningWeighting = 10 + 20 = 30
    116.                 // i = 2: runningWeighting = 30 + 30 = 60
    117.                 // i = 3: runningWeighting = 60 + 40 = 100
    118.                 runningWeighting += weightings[i];
    119.  
    120.                 // There is no interval until the running weighting is greater than zero.
    121.                 if (runningWeighting > 0 && number < runningWeighting)
    122.                 {
    123.                     // The number generated falls within the current weighting interval so get the value from the original range that corresponds to that interval.
    124.                     return minValue + i;
    125.                 }
    126.             }
    127.  
    128.             // If we end up here then something was wrong with the interval and/or the weightings.
    129.             // The validation at the top of the method should ensure that such cases are always caught first.
    130.             throw new Exception("An unexpected error occurred.");
    131.         }
    132.  
    133.         /// <summary>
    134.         /// Returns a non-negative random integer that is less than the specified maximum where each value in that range has a weighted probability.
    135.         /// </summary>
    136.         /// <param name="source">
    137.         /// The <see cref="Random"/> object to use to generate the number.
    138.         /// </param>
    139.         /// <param name="maxValue">
    140.         /// The exclusive upper bound of the random number to be generated. maxValue must be greater than or equal to 0.
    141.         /// </param>
    142.         /// <param name="weightings">
    143.         /// A dictionary containing the weightings for some or all of the possible outcomes, where the keys are the possible outcomes and the values are the weightings.
    144.         /// </param>
    145.         /// <returns>
    146.         /// A 32-bit signed integer that is greater than or equal to 0, and less than maxValue; that is, the range of return values ordinarily includes 0 but not maxValue. However, if maxValue equals 0, maxValue is returned.
    147.         /// </returns>
    148.         /// <remarks>
    149.         /// A weighting of 1 is used for any values for which a weighting is not explicitly provided.  Weightings are a proportion of the total of all weightings.  They are not percentages.
    150.         /// For instance, if there are three possible outcomes and the weightings are 1, 2 and 3 then the first outcome will result in about 1/6 of the time, the second outcome will result about 1/3 of the time and the third outcome will result about 1/2 of the time.
    151.         /// </remarks>
    152.         public static int NextWithWeighting(this Random source,
    153.                                             int maxValue,
    154.                                             IDictionary<int,int> weightings)
    155.         {
    156.             return source.NextWithWeighting(0, maxValue, weightings);
    157.         }
    158.  
    159.         /// <summary>
    160.         /// Returns a random integer that is within a specified range where each value in that range has a weighted probability.
    161.         /// </summary>
    162.         /// <param name="source">
    163.         /// The <see cref="Random"/> object to use to generate the number.
    164.         /// </param>
    165.         /// <param name="minValue">
    166.         /// The inclusive lower bound of the random number returned.
    167.         /// </param>
    168.         /// <param name="maxValue">
    169.         /// The exclusive upper bound of the random number returned. maxValue must be greater than or equal to minValue.
    170.         /// </param>
    171.         /// <param name="weightings">
    172.         /// A dictionary containing the weightings for some or all of the possible outcomes, where the keys are the possible outcomes and the values are the weightings.
    173.         /// </param>
    174.         /// <returns>
    175.         /// A 32-bit signed integer greater than or equal to minValue and less than maxValue; that is, the range of return values includes minValue but not maxValue. If minValue equals maxValue, minValue is returned.
    176.         /// </returns>
    177.         /// <remarks>
    178.         /// A weighting of 1 is used for any values for which a weighting is not explicitly provided.  Weightings are a proportion of the total of all weightings.  They are not percentages.
    179.         /// For instance, if there are three possible outcomes and the weightings are 1, 2 and 3 then the first outcome will result in about 1/6 of the time, the second outcome will result about 1/3 of the time and the third outcome will result about 1/2 of the time.
    180.         /// </remarks>
    181.         public static int NextWithWeighting(this Random source,
    182.                                             int minValue,
    183.                                             int maxValue,
    184.                                             IDictionary<int, int> weightings)
    185.         {
    186.             return source.NextWithWeighting(minValue, maxValue, weightings, DEFAULT_WEIGHTING);
    187.         }
    188.  
    189.         /// <summary>
    190.         /// Returns a non-negative random integer that is less than the specified maximum where each value in that range has a weighted probability.
    191.         /// </summary>
    192.         /// <param name="source">
    193.         /// The <see cref="Random"/> object to use to generate the number.
    194.         /// </param>
    195.         /// <param name="maxValue">
    196.         /// The exclusive upper bound of the random number to be generated. maxValue must be greater than or equal to 0.
    197.         /// </param>
    198.         /// <param name="weightings">
    199.         /// A dictionary containing the weightings for some or all of the possible outcomes, where the keys are the possible outcomes and the values are the weightings.
    200.         /// </param>
    201.         /// <param name="defaultWeighting">
    202.         /// Thw weighting to be used for those values for which one is not explicitly provided.
    203.         /// </param>
    204.         /// <returns>
    205.         /// A 32-bit signed integer that is greater than or equal to 0, and less than maxValue; that is, the range of return values ordinarily includes 0 but not maxValue. However, if maxValue equals 0, maxValue is returned.
    206.         /// </returns>
    207.         /// <remarks>
    208.         /// Weightings are a proportion of the total of all weightings.  They are not percentages.
    209.         /// For instance, if there are three possible outcomes and the weightings are 1, 2 and 3 then the first outcome will result in about 1/6 of the time, the second outcome will result about 1/3 of the time and the third outcome will result about 1/2 of the time.
    210.         /// </remarks>
    211.         public static int NextWithWeighting(this Random source,
    212.                                             int maxValue,
    213.                                             IDictionary<int, int> weightings,
    214.                                             int defaultWeighting)
    215.         {
    216.             return source.NextWithWeighting(0, maxValue, weightings, defaultWeighting);
    217.         }
    218.  
    219.         /// <summary>
    220.         /// Returns a random integer that is within a specified range where each value in that range has a weighted probability.
    221.         /// </summary>
    222.         /// <param name="source">
    223.         /// The <see cref="Random"/> object to use to generate the number.
    224.         /// </param>
    225.         /// <param name="minValue">
    226.         /// The inclusive lower bound of the random number returned.
    227.         /// </param>
    228.         /// <param name="maxValue">
    229.         /// The exclusive upper bound of the random number returned. maxValue must be greater than or equal to minValue.
    230.         /// </param>
    231.         /// <param name="weightings">
    232.         /// A dictionary containing the weightings for some or all of the possible outcomes, where the keys are the possible outcomes and the values are the weightings.
    233.         /// </param>
    234.         /// <param name="defaultWeighting">
    235.         /// Thw weighting to be used for those values for which one is not explicitly provided.
    236.         /// </param>
    237.         /// <returns>
    238.         /// A 32-bit signed integer greater than or equal to minValue and less than maxValue; that is, the range of return values includes minValue but not maxValue. If minValue equals maxValue, minValue is returned.
    239.         /// </returns>
    240.         /// <remarks>
    241.         /// Weightings are a proportion of the total of all weightings.  They are not percentages.
    242.         /// For instance, if there are three possible outcomes and the weightings are 1, 2 and 3 then the first outcome will result in about 1/6 of the time, the second outcome will result about 1/3 of the time and the third outcome will result about 1/2 of the time.
    243.         /// </remarks>
    244.         public static int NextWithWeighting(this Random source,
    245.                                             int minValue,
    246.                                             int maxValue,
    247.                                             IDictionary<int, int> weightings,
    248.                                             int defaultWeighting)
    249.         {
    250.             if (minValue > maxValue)
    251.             {
    252.                 throw new ArgumentOutOfRangeException($"'{nameof(minValue)}' cannot be greater than {nameof(maxValue)}.", nameof(minValue));
    253.             }
    254.  
    255.             if (weightings.Keys.Any(k => k < minValue ||
    256.                                          minValue == maxValue && k > maxValue ||
    257.                                          minValue < maxValue && k >= maxValue))
    258.             {
    259.                 throw new ArgumentException("Weightings cannot be provided for values outside the specified range.");
    260.             }
    261.  
    262.             if (defaultWeighting < 0)
    263.             {
    264.                 throw new ArgumentOutOfRangeException(nameof(defaultWeighting), defaultWeighting, "Value cannot be less than zero.");
    265.             }
    266.  
    267.             var allWeightings = new List<int>
    268.                                 {
    269.                                     weightings.TryGetValue(minValue, out var weighting)
    270.                                         ? weighting
    271.                                         : defaultWeighting
    272.                                 };
    273.  
    274.             for (var i = minValue + 1; i < maxValue; i++)
    275.             {
    276.                 allWeightings.Add(weightings.TryGetValue(i, out weighting)
    277.                                       ? weighting
    278.                                       : defaultWeighting);
    279.             }
    280.  
    281.             return source.NextWithWeighting(minValue, maxValue, allWeightings.ToArray());
    282.         }
    283.     }
    284. }
    The overloads of NextWithWeighting correspond to the overloads of Random.Next that have parameters. Like those Next overloads, the 'maxValue' parameter is exclusive while the 'minValue' parameter is inclusive. NextWithWeighting requires that a weighting is provided for every possible outcome. I plan to add overloads that allow you to provide weightings for just specific values and have a default for everything else.

    As an example, the following code generates random numbers that are greater than or equal to zero and less than 4. The possible outcomes of 0, 1, 2 and 3 have weightings of 1, 2, 3 and 4 respectively. That means that, for every 1 time that value 0 is generated, the value 1 should be generated twice, the value 2 should be generated three times and the value 3 should be generated four times.
    Code:
    var rng = new Random();
    var results = new Dictionary<int, int> {{0, 0}, {1, 0}, {2, 0}, {3, 0}};
    
    for (var i = 0; i < 1000; i++)
    {
        var number = rng.NextWithWeighting(4, new[] {1, 2, 3, 4});
    
        results[number]++;
    }
    
    foreach (var kvp in results)
    {
        Console.WriteLine("{0} was generated {1} times.", kvp.Key, kvp.Value);
    }
    If you run that code multiple times and check the output, you'll see that the specific results will vary but they are close to those proportions each time, i.e. 0 is generated about 100 out of 1000 times, 1 is generated about 200 times, 2 is generated about 300 times and 3 is generated about 400 times.

    The following code is similar but specifies a lower bound for the range and less regular weightings:
    Code:
    var rng = new Random();
    var results = new Dictionary<int, int>();
    
    for (var i = 8; i < 14; i++)
    {
        results.Add(i, 0);
    }
    
    var weightings = new[] {11, 5, 3, 21, 65, 26};
    
    for (var i = 0; i < weightings.Sum() * 100; i++)
    {
        var number = rng.NextWithWeighting(8, 14, weightings);
    
        results[number]++;
    }
    
    foreach (var kvp in results)
    {
        Console.WriteLine("{0} was generated {1} times.", kvp.Key, kvp.Value);
    }
    Again, if you run that code multiple times, you should see that the proportions in which the values are generated is always approximately the same as specified by the weightings.

    Note that the weightings are not percentages and so they do not have to sum to 100. They are simply relative proportions. That means that weightings of 1, 2 and 3 are perfectly equivalent to weightings of 347, 694 and 1041.

  4. #4

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Useful Extension Methods for DateTimes

    csharp Code:
    1. // Gets a value indicating whether a <see cref="DateTime"/> value represents a week day.
    2. public static bool IsWeekDay(this DateTime source)
    3. // Returns a new <see cref="DateTime"/> that adds the specified number of week days to a specified value.
    4. public static DateTime AddWeekDays(this DateTime source, double value)
    The methods in the DateTime project allow you to easily add a number of days to a DateTime while ignoring weekends.
    csharp Code:
    1. using System;
    2.  
    3. namespace Wunnell.Extensions
    4. {
    5.     /// <summary>
    6.     /// Contains methods that extend the <see cref="DateTime"/> structure.
    7.     /// </summary>
    8.     public static class DateTimeExtensions
    9.     {
    10.         /// <summary>
    11.         /// Gets a value indicating whether a <see cref="DateTime"/> value represents a week day.
    12.         /// </summary>
    13.         /// <param name="source">
    14.         /// The input <see cref="DateTime"/>, which acts as the <b>this</b> instance for the extension method.
    15.         /// </param>
    16.         /// <returns>
    17.         /// <b>true</b> if the represents a week day; otherwise <b>false</b>.
    18.         /// </returns>
    19.         /// <remarks>
    20.         /// All days other than Saturday and Sunday are considered week days.
    21.         /// </remarks>
    22.         public static bool IsWeekDay(this DateTime source)
    23.         {
    24.             return source.DayOfWeek != DayOfWeek.Saturday &&
    25.                    source.DayOfWeek != DayOfWeek.Sunday;
    26.         }
    27.  
    28.         /// <summary>
    29.         /// Returns a new <see cref="DateTime"/> that adds the specified number of week days to a specified value.
    30.         /// </summary>
    31.         /// <param name="source">
    32.         /// The input <see cref="DateTime"/>, which acts as the <b>this</b> instance for the extension method.
    33.         /// </param>
    34.         /// <param name="value">
    35.         /// A number of whole and fractional days. The <i>value</i> parameter can be negative or positive.
    36.         /// </param>
    37.         /// <returns>
    38.         /// An object whose value is the sum of the date and time represented by this instance and the number of week days represented by <i>value</i>.
    39.         /// </returns>
    40.         /// <remarks>
    41.         /// All days other than Saturday and Sunday are considered week days.
    42.         /// </remarks>
    43.         public static DateTime AddWeekDays(this DateTime source, double value)
    44.         {
    45.             // A unit will be +/- 1 day.
    46.             var unit = Math.Sign(value) * 1.0;
    47.  
    48.             // Start increasing the date by units from the initial date.
    49.             var result = source;
    50.  
    51.             // When testing for zero, allow a margin for precision error.
    52.             while (!value.IsEquivalentTo(0.0, 0.0001))
    53.             {
    54.                 if (Math.Abs(value) < 1.0)
    55.                 {
    56.                     // There is less than one full day to add so we need to see whether adding it will take us past midnight.
    57.                     var temp = result.AddDays(value);
    58.  
    59.                     if (temp.Date == result.Date || temp.IsWeekDay())
    60.                     {
    61.                         // Adding the partial day did not take us into a weekend day so we're done.
    62.                         result = temp;
    63.                         value = 0.0;
    64.                     }
    65.                     else
    66.                     {
    67.                         // Adding the partial day took us into a weekend day so we need to add another day.
    68.                         result = result.AddDays(unit);
    69.                     }
    70.                 }
    71.                 else
    72.                 {
    73.                     // Add a single day.
    74.                     result = result.AddDays(unit);
    75.  
    76.                     if (result.IsWeekDay())
    77.                     {
    78.                         // Adding a day did not take us into a weekend day so we can reduce the remaining value to add.
    79.                         value -= unit;
    80.                     }
    81.                 }
    82.             }
    83.  
    84.             return result;
    85.         }
    86.     }
    87. }
    The AddWeekDays method works in the same way as the DateTime.AddDays method in that it can take a positive or negative, whole or fractional number of days and create a new DateTime by adding that number of days to the original Date. The following code compares the two methods:
    Code:
    var startDate = new DateTime(2018, 4, 19, 11, 0, 0);
    
    for (var i = 0.5; i <= 10.0; i += 0.5)
    {
        var endDate = startDate.AddDays(i);
    
        Console.WriteLine("{0:ddd, d MMM yyyy h:mm tt} + {1:0.0} days =      {2:ddd, d MMM yyyy h:mm tt}", startDate, i, endDate);
    
        endDate = startDate.AddWeekDays(i);
    
        Console.WriteLine("{0:ddd, d MMM yyyy h:mm tt} + {1:0.0} week days = {2:ddd, d MMM yyyy h:mm tt}", startDate, i, endDate);
    }
    That code produces the following output:
    Code:
    Thu, 19 Apr 2018 11:00 AM + 0.5 days =      Thu, 19 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 0.5 week days = Thu, 19 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 1.0 days =      Fri, 20 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 1.0 week days = Fri, 20 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 1.5 days =      Fri, 20 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 1.5 week days = Fri, 20 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 2.0 days =      Sat, 21 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 2.0 week days = Mon, 23 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 2.5 days =      Sat, 21 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 2.5 week days = Mon, 23 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 3.0 days =      Sun, 22 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 3.0 week days = Tue, 24 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 3.5 days =      Sun, 22 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 3.5 week days = Tue, 24 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 4.0 days =      Mon, 23 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 4.0 week days = Wed, 25 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 4.5 days =      Mon, 23 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 4.5 week days = Wed, 25 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 5.0 days =      Tue, 24 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 5.0 week days = Thu, 26 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 5.5 days =      Tue, 24 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 5.5 week days = Thu, 26 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 6.0 days =      Wed, 25 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 6.0 week days = Fri, 27 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 6.5 days =      Wed, 25 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 6.5 week days = Fri, 27 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 7.0 days =      Thu, 26 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 7.0 week days = Mon, 30 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 7.5 days =      Thu, 26 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 7.5 week days = Mon, 30 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 8.0 days =      Fri, 27 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 8.0 week days = Tue, 1 May 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 8.5 days =      Fri, 27 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 8.5 week days = Tue, 1 May 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 9.0 days =      Sat, 28 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 9.0 week days = Wed, 2 May 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 9.5 days =      Sat, 28 Apr 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 9.5 week days = Wed, 2 May 2018 11:00 PM
    Thu, 19 Apr 2018 11:00 AM + 10.0 days =      Sun, 29 Apr 2018 11:00 AM
    Thu, 19 Apr 2018 11:00 AM + 10.0 week days = Thu, 3 May 2018 11:00 AM

  5. #5

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Useful Extension Methods for Enumerable Lists

    csharp Code:
    1. // Randomises the items in an enumerable list.public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
    The method in the Enumerable project allows you to order an enumerable list of items randomly.
    csharp Code:
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4.  
    5. namespace Wunnell.Extensions
    6. {
    7.     /// <summary>
    8.     /// Contains methods that extend the <see cref="IEnumerable{T}"/> interface.
    9.     /// </summary>
    10.     public static class EnumerableExtensions
    11.     {
    12.         /// <summary>
    13.         /// A random number generator.
    14.         /// </summary>
    15.         private static Random rng;
    16.  
    17.         /// <summary>
    18.         /// Randomises the items in an enumerable list.
    19.         /// </summary>
    20.         /// <typeparam name="T">
    21.         /// The type of the items in the list.
    22.         /// </typeparam>
    23.         /// <param name="source">
    24.         /// The input list.
    25.         /// </param>
    26.         /// <returns>
    27.         /// The items from the input list in random order.
    28.         /// </returns>
    29.         public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
    30.         {
    31.             EnsureRandomInitialized();
    32.  
    33.             return source.OrderBy(o => rng.NextDouble());
    34.         }
    35.  
    36.         /// <summary>
    37.         /// Initialises the random number generator if it is not already.
    38.         /// </summary>
    39.         private static void EnsureRandomInitialized()
    40.         {
    41.             rng = rng ?? new Random();
    42.         }
    43.     }
    44. }
    Note that this method cannot be used to randomise an array or collection in-place. If called on an object implementing IList(Of T), it doesn't not affect the order of the items in that list, but exposes those items as an enumerable list in random order. That list can be enumerated directly or used as the source for another array or collection. For example, you could use this method to simulate a lottery draw like so:
    Code:
    var allNumbers = Enumerable.Range(1, 40);
    var selectedNumbers = allNumbers.Randomize().Take(6);
    
    Console.WriteLine("Your winning lottery numbers tonight are: " + string.Join(", ", selectedNumbers));
    You might also use this method to shuffle a deck of cards:
    Code:
    var values = new[] {"Ace", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Queen", "King"};
    var suits = new[] {"Hearts", "Clubs", "Diamonds", "Spades"};
    var allCards = from value in values
                   from suit in suits
                   select $"{value} of {suit}";
    var deck = new Stack<string>(allCards.Randomize());
    var hand = new List<string>();
    
    for (var i = 0; i < 5; i++)
    {
        hand.Add(deck.Pop());
    }
    
    Console.WriteLine("Your hand contains: " + string.Join(", ", hand));

  6. #6

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Re: Useful Extension Methods

    Updated attachment. See edit to post #1.

  7. #7

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Re: Useful Extension Methods

    Updated attachment. See edit to post #1.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width