برچسب: different

  • IFormattable interface, to define different string formats for the same object | Code4IT

    IFormattable interface, to define different string formats for the same object | Code4IT


    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    Even when the internal data is the same, sometimes you can represent it in different ways. Think of the DateTime structure: by using different modifiers, you can represent the same date in different formats.

    DateTime dt = new DateTime(2024, 1, 1, 8, 53, 14);
    
    Console.WriteLine(dt.ToString("yyyy-MM-dddd")); //2024-01-Monday
    Console.WriteLine(dt.ToString("Y")); //January 2024
    

    Same datetime, different formats.

    You can further customise it by adding the CultureInfo:

    System.Globalization.CultureInfo italianCulture = new System.Globalization.CultureInfo("it-IT");
    
    Console.WriteLine(dt.ToString("yyyy-MM-dddd", italianCulture)); //2024-01-lunedì
    Console.WriteLine(dt.ToString("Y", italianCulture)); //gennaio 2024
    

    Now, how can we use this behaviour in our custom classes?

    IFormattable interface for custom ToString definition

    Take this simple POCO class:

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime BirthDate { get; set; }
    }
    

    We can make this class implement the IFormattable interface so that we can define and use the advanced ToString:

    public class Person : IFormattable
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime BirthDate { get; set; }
    
        public string ToString(string? format, IFormatProvider? formatProvider)
        {
            // Here, you define how to work with different formats
        }
    }
    

    Now, we can define the different formats. Since I like to keep the available formats close to the main class, I added a nested class that only exposes the names of the formats.

    public class Person : IFormattable
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime BirthDate { get; set; }
    
        public string ToString(string? format, IFormatProvider? formatProvider)
        {
            // Here, you define how to work with different formats
        }
    
        public static class StringFormats
        {
            public const string FirstAndLastName = "FL";
            public const string Mini = "Mini";
            public const string Full = "Full";
        }
    }
    

    Finally, we can implement the ToString(string? format, IFormatProvider? formatProvider) method, taking care of all the different formats we support (remember to handle the case when the format is not recognised!)

    public string ToString(string? format, IFormatProvider? formatProvider)
    {
        switch (format)
        {
            case StringFormats.FirstAndLastName:
                return string.Format("{0} {1}", FirstName, LastName);
            case StringFormats.Full:
            {
                FormattableString fs = $"{FirstName} {LastName} ({BirthDate:D})";
                return fs.ToString(formatProvider);
            }
            case StringFormats.Mini:
                return $"{FirstName.Substring(0, 1)}.{LastName.Substring(0, 1)}";
            default:
                return this.ToString();
        }
    }
    

    A few things to notice:

    1. I use a switch statement based on the values defined in the StringFormats subclass. If the format is empty or unrecognised, this method returns the default implementation of ToString.
    2. You can use whichever way to generate a string, like string interpolation, or more complex ways;
    3. In the StringFormats.Full branch, I stored the string format in a FormattableString instance to apply the input formatProvider to the final result.

    Getting a custom string representation of an object

    We can try the different formatting options now that we have implemented them all.

    Look at how the behaviour changes based on the formatting and input culture (Hint: venerdí is the Italian for Friday.).

    Person person = new Person
    {
        FirstName = "Albert",
        LastName = "Einstein",
        BirthDate = new DateTime(1879, 3, 14)
    };
    
    System.Globalization.CultureInfo italianCulture = new System.Globalization.CultureInfo("it-IT");
    
    Console.WriteLine(person.ToString(Person.StringFormats.FirstAndLastName, italianCulture)); //Albert Einstein
    
    Console.WriteLine(person.ToString(Person.StringFormats.Mini, italianCulture)); //A.E
    
    Console.WriteLine(person.ToString(Person.StringFormats.Full, italianCulture)); //Albert Einstein (venerdì 14 marzo 1879)
    
    Console.WriteLine(person.ToString(Person.StringFormats.Full, null)); //Albert Einstein (Friday, March 14, 1879)
    
    Console.WriteLine(person.ToString(Person.StringFormats.Full, CultureInfo.InvariantCulture)); //Albert Einstein (Friday, 14 March 1879)
    
    Console.WriteLine(person.ToString("INVALID FORMAT", CultureInfo.InvariantCulture)); //Scripts.General.IFormattableTest+Person
    
    Console.WriteLine(string.Format("I am {0:Mini}", person)); //I am A.E
    
    Console.WriteLine($"I am not {person:Full}"); //I am not Albert Einstein (Friday, March 14, 1879)
    

    Not only that, but now the result can also depend on the Culture related to the current thread:

    using (new TemporaryThreadCulture(italianCulture))
    {
        Console.WriteLine(person.ToString(Person.StringFormats.Full, CultureInfo.CurrentCulture)); // Albert Einstein (venerdì 14 marzo 1879)
    }
    
    using (new TemporaryThreadCulture(germanCulture))
    {
        Console.WriteLine(person.ToString(Person.StringFormats.Full, CultureInfo.CurrentCulture)); //Albert Einstein (Freitag, 14. März 1879)
    }
    

    (note: TemporaryThreadCulture is a custom class that I explained in a previous article – see below)

    Further readings

    You might be thinking «wow, somebody still uses String.Format? Weird!»

    Well, even though it seems an old-style method to generate strings, it’s still valid, as I explain here:

    🔗How to use String.Format – and why you should care about it | Code4IT

    Also, how did I temporarily change the culture of the thread? Here’s how:
    🔗 C# Tip: How to temporarily change the CurrentCulture | Code4IT

    This article first appeared on Code4IT 🐧

    Wrapping up

    I hope you enjoyed this article! Let’s keep in touch on Twitter or LinkedIn! 🤜🤛

    Happy coding!

    🐧





    Source link

  • Path.Combine and Path.Join are similar but way different. | Code4IT

    Path.Combine and Path.Join are similar but way different. | Code4IT


    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    When you need to compose the path to a folder or file location, you can rely on the Path class. It provides several static methods to create, analyze and modify strings that represent a file system.

    Path.Join and Path.Combine look similar, yet they have some important differences that you should know to get the result you are expecting.

    Path.Combine: take from the last absolute path

    Path.Combine concatenates several strings into a single string that represents a file path.

    Path.Combine("C:", "users", "davide");
    // C:\users\davide
    

    However, there’s a tricky behaviour: if any argument other than the first contains an absolute path, all the previous parts are discarded, and the returned string starts with the last absolute path:

    Path.Combine("foo", "C:bar", "baz");
    // C:bar\baz
    
    Path.Combine("foo", "C:bar", "baz", "D:we", "ranl");
    // D:we\ranl
    

    Path.Join: take everything

    Path.Join does not try to return an absolute path, but it just joins the string using the OS path separator:

    Path.Join("C:", "users", "davide");
    // C:\users\davide
    

    This means that if there is an absolute path in any argument position, all the previous parts are not discarded:

    Path.Join("foo", "C:bar", "baz");
    // foo\C:bar\baz
    
    Path.Join("foo", "C:bar", "baz", "D:we", "ranl");
    // foo\C:bar\baz\D:we\ranl
    

    Final comparison

    As you can see, the behaviour is slightly different.

    Let’s see a table where we call the two methods using the same input strings:

    Path.Combine Path.Join
    ["singlestring"] singlestring singlestring
    ["foo", "bar", "baz"] foo\bar\baz foo\bar\baz
    ["foo", " bar ", "baz"] foo\ bar \baz foo\ bar \baz
    ["C:", "users", "davide"] C:\users\davide C:\users\davide
    ["foo", " ", "baz"] foo\ \baz foo\ \baz
    ["foo", "C:bar", "baz"] C:bar\baz foo\C:bar\baz
    ["foo", "C:bar", "baz", "D:we", "ranl"] D:we\ranl foo\C:bar\baz\D:we\ranl
    ["C:", "/users", "/davide"] /davide C:/users/davide
    ["C:", "users/", "/davide"] /davide C:\users//davide
    ["C:", "\users", "\davide"] \davide C:\users\davide

    Have a look at some specific cases:

    • neither methods handle white and empty spaces: ["foo", " ", "baz"] are transformed to foo\ \baz. Similarly, ["foo", " bar ", "baz"] are combined into foo\ bar \baz, without removing the head and trail whitespaces. So, always remove white spaces and empty values!
    • Path.Join handles in a not-so-obvious way the case of a path starting with / or \: if a part starts with \, it is included in the final path; if it starts with /, it is escaped as //. This behaviour depends on the path separator used by the OS: in my case, I’m running these methods using Windows 11.

    Finally, always remember that the path separator depends on the Operating System that is running the code. Don’t assume that it will always be /: this assumption may be correct for one OS but wrong for another one.

    This article first appeared on Code4IT 🐧

    Wrapping up

    As we have learned, Path.Combine and Path.Join look similar but have profound differences.

    Dealing with path building may look easy, but it hides some complexity. Always remember to:

    • validate and clean your input before using either of these methods (remove empty values, white spaces, and head or trailing path separators);
    • always write some Unit Tests to cover all the necessary cases;

    I hope you enjoyed this article! Let’s keep in touch on Twitter or LinkedIn! 🤜🤛

    Happy coding!

    🐧





    Source link