Units.NET add unit without recompiling - nuget

We have a need to convert between units of measure on the fly, and Units.NET seems like the ideal solution, however, it is lacking some of the units we need (gallons per minute). I know it is trivial to add to the specification file and recreate the classes, but it would be preferable to be able to simply use the nuget package. Does anyone know of a way to add units to Units.NET without recompiling the Units.NET solution?
It should be noted that we aren't married to Units.NET, we just need to be able to convert between arbitrary units of flow and length.
Thanks!

You will probably get a better response asking this on Units.NET's github issues page. There is a wiki page on adding custom units outside the main lib, but I admit this part is not very polished and could use improvement.
Copied from the wiki:
If you are looking to add new quantities or units to the UnitsNet nuget, please see https://github.com/angularsen/UnitsNet/wiki/Adding-a-New-Unit.
Units.NET structure
Units.NET roughly consists of these parts:
Quantities like Length and Force
Unit enum values like LengthUnit.Meter and ForceUnit.Newton
UnitAbbreviationsCache, UnitParser, QuantityParser and UnitConverter for parsing and converting quantities and units
JSON files for defining units, conversion functions and abbreviations
CodeGen.exe to generate C# code based on JSON files
Example: Custom quantity HowMuch with units HowMuchUnit
Map unit enum values to unit abbreviations
UnitAbbreviationsCache.Default.MapUnitToDefaultAbbreviation(HowMuchUnit.Some, "sm");
UnitAbbreviationsCache.Default.MapUnitToDefaultAbbreviation(HowMuchUnit.Lots, "lts");
UnitAbbreviationsCache.Default.MapUnitToDefaultAbbreviation(HowMuchUnit.Tons, "tns");
Lookup unit abbrevations from enum values
Console.WriteLine("GetDefaultAbbreviation(): " + string.Join(", ",
UnitAbbreviationsCache.Default.GetDefaultAbbreviation(HowMuchUnit.Some), // "sm"
UnitAbbreviationsCache.Default.GetDefaultAbbreviation(HowMuchUnit.Lots), // "lts"
UnitAbbreviationsCache.Default.GetDefaultAbbreviation(HowMuchUnit.Tons) // "tns"
));
Parse unit abbreviations back to enum values
Console.WriteLine("Parse<HowMuchUnit>(): " + string.Join(", ",
UnitParser.Default.Parse<HowMuchUnit>("sm"), // Some
UnitParser.Default.Parse<HowMuchUnit>("lts"), // Lots
UnitParser.Default.Parse<HowMuchUnit>("tns") // Tons
));
Convert between units of custom quantity
var unitConverter = UnitConverter.Default;
unitConverter.SetConversionFunction<HowMuch>(HowMuchUnit.Lots, HowMuchUnit.Some, x => new HowMuch(x.Value * 2, HowMuchUnit.Some));
unitConverter.SetConversionFunction<HowMuch>(HowMuchUnit.Tons, HowMuchUnit.Lots, x => new HowMuch(x.Value * 10, HowMuchUnit.Lots));
unitConverter.SetConversionFunction<HowMuch>(HowMuchUnit.Tons, HowMuchUnit.Some, x => new HowMuch(x.Value * 20, HowMuchUnit.Some));
var from = new HowMuch(10, HowMuchUnit.Tons);
IQuantity Convert(HowMuchUnit toUnit) => unitConverter.GetConversionFunction<HowMuch>(from.Unit, toUnit)(from);
Console.WriteLine($"Convert 10 tons to:");
Console.WriteLine(Convert(HowMuchUnit.Some)); // 200 sm
Console.WriteLine(Convert(HowMuchUnit.Lots)); // 100 lts
Console.WriteLine(Convert(HowMuchUnit.Tons)); // 10 tns
Sample quantity
public enum HowMuchUnit
{
Some,
Lots,
Tons
}
public struct HowMuch : IQuantity
{
public HowMuch(double value, HowMuchUnit unit)
{
Unit = unit;
Value = value;
}
Enum IQuantity.Unit => Unit;
public HowMuchUnit Unit { get; }
public double Value { get; }
#region IQuantity
private static readonly HowMuch Zero = new HowMuch(0, HowMuchUnit.Some);
public QuantityType Type => QuantityType.Undefined;
public BaseDimensions Dimensions => BaseDimensions.Dimensionless;
public QuantityInfo QuantityInfo => new QuantityInfo(Type,
new UnitInfo[]
{
new UnitInfo<HowMuchUnit>(HowMuchUnit.Some, BaseUnits.Undefined),
new UnitInfo<HowMuchUnit>(HowMuchUnit.Lots, BaseUnits.Undefined),
new UnitInfo<HowMuchUnit>(HowMuchUnit.Tons, BaseUnits.Undefined),
},
HowMuchUnit.Some,
Zero,
BaseDimensions.Dimensionless);
public double As(Enum unit) => Convert.ToDouble(unit);
public double As(UnitSystem unitSystem) => throw new NotImplementedException();
public IQuantity ToUnit(Enum unit)
{
if (unit is HowMuchUnit howMuchUnit) return new HowMuch(As(unit), howMuchUnit);
throw new ArgumentException("Must be of type HowMuchUnit.", nameof(unit));
}
public IQuantity ToUnit(UnitSystem unitSystem) => throw new NotImplementedException();
public override string ToString() => $"{Value} {UnitAbbreviationsCache.Default.GetDefaultAbbreviation(Unit)}";
public string ToString(string format, IFormatProvider formatProvider) => $"HowMuch ({format}, {formatProvider})";
public string ToString(IFormatProvider provider) => $"HowMuch ({provider})";
public string ToString(IFormatProvider provider, int significantDigitsAfterRadix) => $"HowMuch ({provider}, {significantDigitsAfterRadix})";
public string ToString(IFormatProvider provider, string format, params object[] args) => $"HowMuch ({provider}, {string.Join(", ", args)})";
#endregion
}

Related

processing data before presentation

I have dataset (from JSON source) with cumulative values. It looks like this:
Could I extract from this dataset delta from last hour or last day (for example, count from 0 since last midnight?)
What you are asking about falls squarely in the realm of process data as it usually comes from control systems aka process controls systems. There may be DCS (Distributed Control Systems) or SCADA out in the field that act as a focal point on receiving data. And there may be a process historian or time-series database for accessing that data, if not on an enterprise level at least not within the process controls network.
Much of the engineering associated with process data has been established for many, many decades. For my examples, I did not want to write too many custom classes so I will use some everyday .NET objects. However, I am adhering to 2 such well-regarded principles about process data:
All times will be in UTC. Usually one does not show the UtcTime until the very last moment when displaying to a local user.
Process Data acknowledges the Quality of a value. While there can be dozens of bad states associated with such Quality, I will use a simple binary approach of good or bad. Since I use double, a value is good as long as it is not double.NaN.
That said, I assume you have a class that looks similar to:
public class JsonDto
{
public string Id { get; set; }
public DateTime Time { get; set; }
public double value { get; set; }
}
Granted your class name may be different, but the main thing is this class holds an individual instance of process data. When you read a JSON file, it will produce a List<jsonDto> instance.
You will need lots of methods to transform the data to something a wee bit more useable in order to get to where the rubber finally meets the road: producing hourly differences. But that requires producing hourly values because there is no guarantee that your recorded values occur exactly on each hour.
ProcessData Class - lots of methods
public static class ProcessData
{
public enum CalculationTimeBasis { Auto = 0, EarliestTime, MostRecentTime, MidpointTime }
public static Dictionary<string, SortedList<DateTime, double>> GetTagTimedValuesMap(IEnumerable<JsonDto> jsonDto)
{
var map = new Dictionary<string, SortedList<DateTime, double>>();
var tagnames = jsonDto.Select(x => x.Id).Distinct().OrderBy(x => x);
foreach (var tagname in tagnames)
{
map.Add(tagname, new SortedList<DateTime, double>());
}
var orderedValues = jsonDto.OrderBy(x => x.Id).ThenBy(x => x.Time.ToUtcTime());
foreach (var item in orderedValues)
{
map[item.Id].Add(item.Time.ToUtcTime(), item.value);
}
return map;
}
public static DateTimeKind UnspecifiedDefaultsTo { get; set; } = DateTimeKind.Utc;
public static DateTime ToUtcTime(this DateTime value)
{
// Unlike ToUniversalTime(), this method assumes any Unspecified Kind may be Utc or Local.
if (value.Kind == DateTimeKind.Unspecified)
{
if (UnspecifiedDefaultsTo == DateTimeKind.Utc)
{
value = DateTime.SpecifyKind(value, DateTimeKind.Utc);
}
else if (UnspecifiedDefaultsTo == DateTimeKind.Local)
{
value = DateTime.SpecifyKind(value, DateTimeKind.Local);
}
}
return value.ToUniversalTime();
}
private static DateTime TruncateTime(this DateTime value, TimeSpan interval) => new DateTime(TruncateTicks(value.Ticks, interval.Ticks)).ToUtcTime();
private static long TruncateTicks(long ticks, long interval) => (interval == 0) ? ticks : (ticks / interval) * interval;
public static SortedList<DateTime, double> GetInterpolatedValues(SortedList<DateTime, double> recordedValues, TimeSpan interval)
{
if (interval <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException($"{nameof(interval)} TimeSpan must be greater than zero");
}
var interpolatedValues = new SortedList<DateTime, double>();
var previous = recordedValues.First();
var intervalTimestamp = previous.Key.TruncateTime(interval);
foreach (var current in recordedValues)
{
if (current.Key == intervalTimestamp)
{
// It's easy when the current recorded value aligns perfectly on the desired interval.
interpolatedValues.Add(current.Key, current.Value);
intervalTimestamp += interval;
}
else if (current.Key > intervalTimestamp)
{
// We do not exactly align at the desired time, so we must interpolate
// between the "last recorded data" BEFORE the desired time (i.e. previous)
// and the "first recorded data" AFTER the desired time (i.e. current).
var interpolatedValue = GetInterpolatedValue(intervalTimestamp, previous, current);
interpolatedValues.Add(interpolatedValue.Key, interpolatedValue.Value);
intervalTimestamp += interval;
}
previous = current;
}
return interpolatedValues;
}
private static KeyValuePair<DateTime, double> GetInterpolatedValue(DateTime interpolatedTime, KeyValuePair<DateTime, double> left, KeyValuePair<DateTime, double> right)
{
if (!double.IsNaN(left.Value) && !double.IsNaN(right.Value))
{
double totalDuration = (right.Key - left.Key).TotalSeconds;
if (Math.Abs(totalDuration) > double.Epsilon)
{
double partialDuration = (interpolatedTime - left.Key).TotalSeconds;
double factor = partialDuration / totalDuration;
double calculation = left.Value + ((right.Value - left.Value) * factor);
return new KeyValuePair<DateTime, double>(interpolatedTime, calculation);
}
}
return new KeyValuePair<DateTime, double>(interpolatedTime, double.NaN);
}
public static SortedList<DateTime, double> GetDeltaValues(SortedList<DateTime, double> values, CalculationTimeBasis timeBasis = CalculationTimeBasis.Auto)
{
const CalculationTimeBasis autoDefaultsTo = CalculationTimeBasis.MostRecentTime;
var deltas = new SortedList<DateTime, double>(capacity: values.Count);
var previous = values.First();
foreach (var current in values.Skip(1))
{
var time = GetTimeForBasis(timeBasis, previous.Key, current.Key, autoDefaultsTo);
var diff = current.Value - previous.Value;
deltas.Add(time, diff);
previous = current;
}
return deltas;
}
private static DateTime GetTimeForBasis(CalculationTimeBasis timeBasis, DateTime earliestTime, DateTime mostRecentTime, CalculationTimeBasis autoDefaultsTo)
{
if (timeBasis == CalculationTimeBasis.Auto)
{
// Different (future) methods calling this may require different interpretations of Auto.
// Thus we leave it to the calling method to declare what Auto means to it.
timeBasis = autoDefaultsTo;
}
switch (timeBasis)
{
case CalculationTimeBasis.EarliestTime:
return earliestTime;
case CalculationTimeBasis.MidpointTime:
return new DateTime((earliestTime.Ticks + mostRecentTime.Ticks) / 2L).ToUtcTime();
case CalculationTimeBasis.MostRecentTime:
return mostRecentTime;
case CalculationTimeBasis.Auto:
default:
return earliestTime;
}
}
}
Usage Example
var inputValues = new List<JsonDto>();
// TODO: Magically populate inputValues
var tagDataMap = ProcessData.GetTagTimedValuesMap(inputValues);
foreach (var item in tagDataMap)
{
// Following would generate hourly differences for the one Tag Id (item.Key)
// by first generating hourly data, and then finding the delta of that.
var hourlyValues = ProcessData.GetInterpolatedValues(item.Value, TimeSpan.FromHours(1));
// Consider the difference between Hour(1) and Hour(2).
// That is, 2 input values will create 1 output value.
// Now you must decide which of the 2 input times you use for the 1 output time.
// This is what I call the CalculationTimeBasis.
// The time basis used will be Auto, which defaults to the most recent for this particular method, e.g. Hour(2)
var deltaValues = ProcessData.GetDeltaValues(hourlyValues);
// Same as above except we explicitly state we want the most recent time, e.g. also Hour(2)
var deltaValues2 = ProcessData.GetDeltaValues(hourlyValues, ProcessData.CalculationTimeBasis.MostRecentTime);
// Here the calculated differences are the same except the now
// timestamp now reflects the earliest time, e.g. Hour(1)
var deltaValues3 = ProcessData.GetDeltaValues(hourlyValues, ProcessData.CalculationTimeBasis.EarliestTime);

IObservable output emitted before input?

The program below attempts to print out words with their respective lengths. It erroneously reports that cat has 6 letters. As I examine the log, it looks like the length of a specific word is emitted BEFORE the word it is based upon is emitted. How is this possible? The length observable is defined as word.select(i=>i.Length) so I don't see how it could produce a result before the word arrives. At first I thought this might be a bug in my logging code, but the behavior of Observable.WithLatestFrom reinforces my belief that something weird is going on here.
Log results:
0001report.Subscribe()
0002first.Subscribe()
0003second.Subscribe()
0002first.OnNext(3)
0003second.OnNext(cat)
0002first.OnNext(6)
0001report.OnNext({ Word = cat, Length = 6 })
0003second.OnNext(donkey)
The program:
static void Main(string[] args) {
ILogger logger = new DelegateLogger(Console.WriteLine);
Subject<string> word = new Subject<string>();
IObservable<int> length = word.Select(i => i.Length);
var report = Observable
.WithLatestFrom(
length.Log(logger, "first"),
word.Log(logger, "second"),
(l, w) => new { Word = w, Length = l })
.Log(logger,"report");
report.Subscribe();
word.OnNext("cat");
word.OnNext("donkey");
Console.ReadLine();
}
public interface ILogger
{
void Log(string input);
}
public class DelegateLogger : ILogger
{
Action<string> _printer;
public DelegateLogger(Action<string> printer) {
_printer = printer;
}
public void Log(string input) => _printer(input);
}
public static class ObservableLoggingExtensions
{
private static int _index = 0;
public static IObservable<T> Log<T>(this IObservable<T> source, ILogger logger, string name) {
return Observable.Create<T>(o => {
var index = Interlocked.Increment(ref _index);
var label = $"{index:0000}{name}";
logger.Log($"{label}.Subscribe()");
var disposed = Disposable.Create(() => logger.Log($"{label}.Dispose()"));
var subscription = source
.Do(
x => logger.Log($"{label}.OnNext({x?.ToString() ?? "null"})"),
ex => logger.Log($"{label}.OnError({ex})"),
() => logger.Log($"{label}.OnCompleted()")
)
.Subscribe(o);
return new CompositeDisposable(subscription, disposed);
});
}
}
I think I know what is going on. There are two subscriptions on word (1. length 2. WithLatestFrom), and one subscription on length (1. WithLatestFrom)
When a word is emitted, a synchronous callback process starts that passes it to the first subscriber (length), which calculates a value, that is passed to its subscriber, WithLatestFrom. Next, WithLatestFrom receives the word that generated the calculated length. So WithLatestFrom receives the length BEFORE the word, not the other way around. That's why the report isn't giving me the results I expected.

cannot implicitly convert type 'system.collections.generic.list<string> ' to 'system.collections.generic.list<entities.tablename>

I tried almost all the solutions but of no use.I am unable to get the distinct list of the particular column Priority.Any help greatly appreciated.
public List<AlertType> GetAllAlertNames()
{
var lstAlertNames = _zyenaDbContext.AlertTypes.Select(x => x.Priority).Distinct().ToList();
return lstAlertNames;
}
The following error occurs with return lstAlertNames:
cannot implicitly convert type 'system.collections.generic.list string '
to 'system.collections.generic.list zyenaEntities.AlertType
In Model AlertType
public string Priority { get; set; }
The .Select(x => x.Priority) is selecting only the Priority property (typeof string), so you are selecting List<string> but your method is returning List<AlertType>.
Not sure what you actually want to return but if it is List<AlertType> morelinq has a useful extension method that allows you to do AlertTypes.DistinctBy(x => x.Priority);
The main mistake is i am trying to return only a single property Priority the entity AlertType is unable to cast.
public List<string> GetDistinctAlertPriorities()
{
var lstAlertNames = _zyenaDbContext.AlertTypes.Select(x => x.Priority).Distinct().ToList();
return lstAlertNames;
}

IComparer using Lambda Expression

class p {
public string Name { get; set; }
public int Age { get; set; }
};
static List<p> ll = new List<p>
{
new p{Name="Jabc",Age=53},new p{Name="Mdef",Age=20},
new p{Name="Exab",Age=45},new p{Name="G123",Age=19}
};
protected static void SortList()
{
IComparer<p> mycomp = (x, y) => x.Name.CompareTo(y.Name); <==(Line 1)
ll.Sort((x, y) => x.Name.CompareTo(y.Name));<==(Line 2)
}
Here the List.sort expects an IComparer<p> as parameter. And it works with the lambda
as shown in Line 2. But when I try to do as in Line 1, I get this error:
Cannot convert lambda expression to
type
System.Collections.Generic.IComparer'
because it is not a delegate type
I investigated this for quite some time but I still don't understand it.Maybe my understanding of IComparer is not quite good.Can somebody give me a hand ?
When you do ll.Sort((x, y) => x.Name.CompareTo(y.Name)); it uses the overload for Comparison<T>, not IComparer. Comparison<T> is a delegate, so you can use a lambda expression for it.
Comparison<p> mycomp = (x, y) => x.Name.CompareTo(y.Name); will work.
There's an existing solution you might refer to: https://stackoverflow.com/a/16839559/371531
This one uses Comparer<T>.Create introduced in .NET Framework 4.5.
IComparer is an interface, not a delegate.
You'll want to use the lambda expression on its .CompareTo(), not on the interface itself.
Use the following simple class:
public static class ComparerUtilities
{
class _Comparer<T> : Comparer<T>
{
Comparison<T> _comparison;
public _Comparer(Comparison<T> comparison)
{
_comparison = comparison;
}
public override int Compare(T x, T y)
{
return _comparison(x, y);
}
}
public static IComparer<T> FromComparison<T>(Comparison<T> comparison)
{
return new _Comparer<T>(comparison);
}
}

C# -Interview Question Anonymous Type

Recently i was asked to prove the power of C# 3.0 in a single line( might be tricky)
i wrote
new int[] { 1, 2, 3 }.Union(new int[]{10,23,45}).
ToList().ForEach(x => Console.WriteLine(x));
and explained you can have (i) anonymous array (ii) extension method (iii)lambda and closure all in a single line.I got spot offer.
But.....
The interviewer asked me how will you convert an anonymous type into known type :(
I explained
public class Product
{
public double ItemPrice { private set; get; }
public string ItemName { private set; get; }
}
var anony=new {ItemName="xxxx",ItemPrice=123.56};
you can't assign product a=anony;
The interviewer replied there is 200% chance to do that
if you have a small work around.I was clueless.
As usual,I am waiting for your valuable reply(Is it possible?).
You're right, you can't make this assignment:
product a=anony;
MSDN: Anonymous Types (C# Programming Guide)
An anonymous type cannot be cast to
any interface or type except for
object.
Maybe something like this:
class Program
{
static T Cast<T>(object target, T example)
{
return (T)target;
}
static object GetAnon()
{
return new { Id = 5 };
}
static void Main()
{
object anon = GetAnon();
var p = Cast(anon, new { Id = 0 });
Console.WriteLine(p.Id);
}
}
Remark: never write or rely on such a code.
May be try the examples shown here..they try to do something similar..
http://www.codeproject.com/KB/linq/AnonymousTypeTransform.aspx
http://www.inflecto.co.uk/Inflecto-Blog/post/2009/11/12/IQueryable-Sorting-Paging-Searching-and-Counting.aspx
var list = anony.Select(x=> new Product {
ItemPrice = x.ItemPrice, ItemName = x.ItemName }).ToList();