Thursday, December 6, 2018

Skip and Take

Skip and Take is not a new form of street crime. It refers to two little known methods of the enumerable interface in the LINQ namespace. I came across them while looking for a solution to the substring's "out of range" exception.

As you know, if you try to execute a statement such as "ABC".SubString(0,5) you will throw an out of range exception. So truncating a string of unknown length becomes tricky. You can check the length of the string first, and this is a perfectly reasonable solution ie.


if (s.Length > 10)
    return s.Substring(0, 10);
else
    return s;


or you can collapse it to


return s.Length > 10 ? s.Substring(0, 10) : s;


or you can blow your co-workers minds with Skip and Take. Let's start by building a solution that demonstrates the problem.

Start by creating a new C# WPF project called SkipAndTake. The XAML looks like this.

<Window x:Class="SkipAndTake.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SkipAndTake"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel Orientation="Vertical">
        <TextBox Text="{Binding TheText, UpdateSourceTrigger=PropertyChanged}" />
        <TextBlock Text="{Binding TheShortText}"/>
        <TextBlock Text="{Binding ErrorText}" Foreground="Red"/>
    </StackPanel>
</Window>


The code behind looks like this.

using System;
using System.ComponentModel;
using System.Windows;

namespace SkipAndTake
{

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private string _TheText = "Type something here";
        public string TheText
        {
            get { return _TheText; }
            set { _TheText = value;
                ErrorText = "";
                NotifyPropertyChanged("TheText");
                NotifyPropertyChanged("TheShortText");
            }
        }

        public string TheShortText
        {
            get
            {
                try
                {
                    return TheText.Substring(0, 10);
                }
                catch (Exception ex)
                {
                    ErrorText = ex.Message;
                    return "";
                }
            }
        }

        private string _ErrorText;
        public string ErrorText
        {
            get { return _ErrorText; }
            set { _ErrorText = value;
                NotifyPropertyChanged("ErrorText");
            }
        }

        public MainWindow()
        {
            InitializeComponent();           
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
    }
}

As you shorten the text in the TextBox you will eventually cause the SubString to throw an exception.



Let's fix the problem by replacing


return TheText.Substring(0, 10);


with


return new String(TheText.Take(10).ToArray());


When we run the application now we can shorten the text as much as we want.


This give us a safe "Left" function but if we add the Skip method we can get a safe SubString function like this.


return new String(TheText.Skip(2).Take(10).ToArray());


Which does exactly what we want, no matter what the length of the string is.


There are also SkipWhile and TakeWhile functions which might be very useful when parsing text. For example, replace

return new String(TheText.Skip(2).Take(10).ToArray());

with


return new String(TheText.SkipWhile(c => c != ',').Skip(1).TakeWhile(c => c != ',').ToArray());

This will search for the first comma, skip it, and return everything up to the next comma. ie it will return everything between the first two commas. For example.


Yes, there are other ways to do all these things, but it's always good to have options.

No comments:

Post a Comment