📜 ⬆️ ⬇️

Daily assistants

Some tasks are encountered when writing code so often that the tools for solving them should be especially convenient. And if the standard tools of the language / development platform seem unsatisfactory, then the programmer writes his own solutions, which, by virtue of frequency of use, become his favorite bicycles. Therefore, the first thing that he does when changing the language / platform is looking for local analogues of his solutions, or the ability to reproduce them.

I present my collection of assistants for solving routine tasks, which developed after migration from C ++ Builder to C #, WPF.

First three


public static class IComparableExtensions { public static T Minv<T>(this T value, T maxValue) where T : IComparable<T> { if (value.CompareTo(maxValue) >= 0) return maxValue; return value; } public static T Maxv<T>(this T value, T minValue) where T : IComparable<T> { if (value.CompareTo(minValue) <= 0) return minValue; return value; } public static T Limit<T>(this T value, T minValue, T maxValue) where T : IComparable<T> { if (value.CompareTo(minValue) <= 0) return minValue; if (value.CompareTo(maxValue) >= 0) return maxValue; return value; } } 

Why did the standard Math.Min and Math.Max ​​turn out to be uncomfortable for me ?

1. The need to use the class name Math before Min and Max . It was so annoying when working with code containing a large number of these functions that I redefined them inside a class.
')
2. The need to use the name of the Math class before Min and Max and the inconvenience due to the conceptual feeling that these functions have no place in this class. I needed any other Math functions only for working with geometry, but Min and Max are counters and indices, that's all for us! And the description of the methods above obviously shows this.

3. The standard notation of the Min and Max functions with large nesting seems to me to be insufficiently readable. I would prefer to have the binary operators min and max . The methods defined above are the closest approach to the desired.

And finally, I always need effort to figure out which function to use to restrict above or below. The Limit method saves me from these difficulties.

In C # 6, you can write using System.Math; and use functions without prefix. Thanks, but late.

Stringmaker


For debugging or logging it is often necessary to simply list some values ​​separated by spaces.
And for this you can write a method like void OutDebug(params object[] args) with simple logic inside. But when there are several classes with this method, another solution is required.

  public class SM { StringBuilder Sb; SM() { Sb = new StringBuilder(); } SM Add(object value) { if (value==null) return this; var objects = value as IEnumerable; if (objects!=null) { foreach (var obj in objects) Add(obj); } else Sb.Append(value.ToString()); return this; } public override string ToString() { return Sb.ToString(); } public static implicit operator string(SM value) { return value==null ? null : value.ToString(); } public static SM operator +(SM a, object b) { return a.Add(b); } public static SM operator -(SM a, object b) { Sb.Append(' '); return Add(b); } public static SM New { get { return new SM(); } } } public static class IEnumerableExtensions { public static IEnumerable Sep(this IEnumerable objects, object separator) { bool first = true; foreach (var obj in objects) { if (first) first = false; else yield return separator; yield return obj; } yield break; } } 

We use:

  var sm = SM.New+""-1-2-3; var rr = new int[] { 1, 2, 3 }; sm = sm+" ("+rr.Sep(", ")+')'; Trace.WriteLine(sm); 

In C # 6, it is possible to enter arguments inside a string. Thanks, but late.

Timertask


In an event-driven environment, it is sometimes necessary to continue working after processing accumulated events. Usually, to display intermediate results of work.

You can do this directly in WPF:

 Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { })); 

And you can arrange the code as an iterator, and execute it using the following class:

  public class TimerTask { public bool IsPaused, IsCancelled; DateTimeOffset NextTime; TimeSpan Interval; Func<bool> Func; static DispatcherTimer Timer; static List<TimerTask> TaskList; TimerTask (double interval, double delay, Func<bool> func) { if (TaskList==null) { TaskList = new List<TimerTask>(); Timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(0.02), IsEnabled = true }; Timer.Tick += Timer_Tick; } TaskList.Add(this); Interval = TimeSpan.FromSeconds(interval); NextTime = DateTimeOffset.Now+TimeSpan.FromSeconds(delay); Func = func; } static void Timer_Tick(object sender, EventArgs ea) { int i = 0, cnt = TaskList.Count; while (i<cnt) { if (TaskList[i].IsCancelled) { TaskList.RemoveAt(i); cnt--; } else { TaskList[i].Tick(); i++; } } } void Tick() { if (IsPaused || DateTimeOffset.Now<NextTime) return; IsCancelled = !Func(); NextTime = DateTimeOffset.Now+Interval; } public static TimerTask DoOnce(Action action, double delay) { return new TimerTask(0, delay, () => { action(); return false; }); } public static TimerTask DoForever(Action action, double interval, double delay) { return new TimerTask(interval, delay, () => { action(); return true; }); } public static TimerTask DoWhile(Func<bool> func, double interval, double delay) { return new TimerTask(interval, delay, () => { return func(); }); } public static TimerTask DoEach(IEnumerable<object> enumerable, double interval, double delay) { var enumerator = enumerable.GetEnumerator(); return new TimerTask(interval, delay, () => { return enumerator.MoveNext(); }); } } 

We use:

  public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); TimerTask.DoEach(Start(), 0, 0); } IEnumerable<object> Start() { Title = "Starting 1"; yield return null; Starting1(); Title = "Starting 2"; yield return null; Starting2(); Title = "Starting 3"; yield return null; Starting3(); Title = "Started"; yield break; } } 

This class can also be used to implement progress dialogs.

The obvious disadvantage of this class is that calling a method like ShowDialog in one of the tasks blocks execution of all the others. This would not have happened if each task had its own instance of DispatcherTimer .

IntRangeNotifyCollection


This class solves a problem that is not exactly frequent, but it is very important for programs that display a large amount of data. If the Virtualization ListBox shows only 40 entries from a collection of 100,000, it is natural to decide that only 40 of these entries are sufficient. It is also desirable to notify the control about changes in the collection only if necessary.

That is, as an ItemsSource you need to substitute not a real collection, but some other class. Most lightweight, which can only be.

  public class IntRangeEnumerator : IEnumerator { int _Current, _Last; public IntRangeEnumerator(int count) : this(0, count) { } public IntRangeEnumerator(int start, int count) { _Current = start-1; _Last = start+count; } public object Current { get { return _Current; } } public bool MoveNext() { _Current++; return _Current<_Last; } public void Dispose() { } public void Reset() { } } public class IntRange : IList { int _Start, _Count; public IntRange(int count) : this(0, count) { } public IntRange(int start, int count) { _Start = start; _Count = count; } public int Count { get { return _Count; } } public IEnumerator GetEnumerator() { return new IntRangeEnumerator(_Start, _Count); } public object this[int index] { get { return _Start+index; } set { } } 

Other methods
  public bool IsSynchronized { get { return true; } } public object SyncRoot { get { return this; } } public void CopyTo(Array array, int index) { for (int i = 0; i<_Count; i++) array.SetValue(_Start+i, index+i); } public bool IsFixedSize { get { return true; } } public bool IsReadOnly { get { return true; } } public int Add(object value) { return 0; } public void Clear() { } public bool Contains(object value) { if (!(value is int)) return false; int i = (int)value; return i>=_Start && i<_Start+_Count; } public int IndexOf(object value) { if (!(value is int)) return -1; int i = (int)value; return i>=_Start && i<_Start+_Count ? i-_Start : -1; } public void Insert(int index, object value) { } public void Remove(object value) { } public void RemoveAt(int index) { } 


  } public class IntRangeNotifyCollection : IEnumerable, INotifyCollectionChanged { int _Count; public event NotifyCollectionChangedEventHandler CollectionChanged; public IntRangeNotifyCollection() { } public IEnumerator GetEnumerator() { return new IntRangeEnumerator(_Count); } protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (CollectionChanged!=null) CollectionChanged(this, e); } public int Count { get { return _Count; } set { if (value==_Count) return; NotifyCollectionChangedEventArgs e; if (value==0) { e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); } else if (value>_Count) { e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new IntRange(_Count, value-_Count), _Count); } else { e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new IntRange(value, _Count-value), value); } _Count = value; OnCollectionChanged(e); } } } 

How then to link indexes with records? This is highly dependent on the task.

Publications on this topic on Habré:
» Range functionality in ObservableCollection
» Data Virtualization in WPF

Source: https://habr.com/ru/post/313824/


All Articles