|
1 | 1 | // Licensed under MIT No Attribution, see LICENSE file at the root. |
2 | 2 |
|
| 3 | +using System.Globalization; |
| 4 | +using System.Text.Json; |
3 | 5 | using UnitsNet; |
| 6 | +using UnitsNet.Modular; |
| 7 | +using UnitsNet.Modular.Generated; |
4 | 8 | using UnitsNet.Units; |
| 9 | +using CoreQuantity = UnitsNet.Core.IQuantity<double>; |
5 | 10 |
|
6 | 11 | namespace UnitsNet.Modular.Playground; |
7 | 12 |
|
8 | 13 | internal static class Program |
9 | 14 | { |
| 15 | + private static readonly CultureInfo Invariant = CultureInfo.InvariantCulture; |
| 16 | + |
10 | 17 | public static void Main() |
11 | 18 | { |
12 | | - // These types and their relationship operator are generated from the built-in selections. |
13 | | - Length route = Length.FromKilometers(5); |
14 | | - Duration elapsed = Duration.FromMinutes(24); |
15 | | - Speed averageSpeed = route / elapsed; |
| 19 | + Console.WriteLine("Electric delivery-day planner"); |
| 20 | + Console.WriteLine("Mixed-unit route inputs, parcel manifest, driving time, and charging plan."); |
| 21 | + |
| 22 | + // This sample intentionally uses several APIs side by side to illustrate their capabilities. |
| 23 | + // Production code would normally choose the shortest, most natural API for each task instead |
| 24 | + // of exercising every available alternative in one workflow. |
| 25 | + |
| 26 | + // APIs illustrated: Parse(), TryParse(), Sum(), Average(), As(), GetAbbreviation(), and static Convert(). |
| 27 | + // Parse() handles required localized input, while TryParse() avoids exceptions for optional input. |
| 28 | + // Sum() and Average() aggregate mixed units; As() returns only a converted number. Static |
| 29 | + // Convert() is useful when neither the source nor result needs to be a quantity object. |
| 30 | + PrintSection("Read and normalize the route manifest"); |
| 31 | + string[] routeInputs = ["48 km", "31 mi", "72 km"]; |
| 32 | + Length[] routeLegs = routeInputs |
| 33 | + .Select(text => Length.Parse(text, Invariant)) |
| 34 | + .ToArray(); |
| 35 | + |
| 36 | + Length depotDetour = Length.TryParse("12 km", Invariant, out Length parsedDetour) |
| 37 | + ? parsedDetour |
| 38 | + : Length.Zero; |
| 39 | + Length totalRoute = routeLegs |
| 40 | + .Append(depotDetour) |
| 41 | + .Sum(LengthUnit.Kilometer); |
| 42 | + Length averageLeg = routeLegs.Average(LengthUnit.Kilometer); |
| 43 | + double totalRouteMiles = totalRoute.As(LengthUnit.Mile); |
| 44 | + string mileAbbreviation = Length.GetAbbreviation(LengthUnit.Mile, Invariant); |
| 45 | + |
| 46 | + Console.WriteLine($"Route legs: {string.Join(", ", routeLegs.Select(FormatStoredValue))}"); |
| 47 | + Console.WriteLine( |
| 48 | + $"Total: {totalRoute.ToString("0.0", Invariant)} / " + |
| 49 | + $"{totalRouteMiles.ToString("0.0", Invariant)} {mileAbbreviation}"); |
| 50 | + Console.WriteLine($"Average: {averageLeg.ToString("0.0", Invariant)} per scheduled leg"); |
| 51 | + |
| 52 | + double localSpeedLimitMph = Speed.Convert( |
| 53 | + 80, |
| 54 | + SpeedUnit.KilometerPerHour, |
| 55 | + SpeedUnit.MilePerHour); |
| 56 | + Console.WriteLine($"80 km/h local limit = {localSpeedLimitMph.ToString("F1", Invariant)} mph"); |
| 57 | + |
| 58 | + // APIs illustrated: same-quantity addition, cross-quantity division, ToUnit(), and ToString(). |
| 59 | + // Ordinary arithmetic preserves the quantity type. Cross-quantity operators produce a different |
| 60 | + // type and are generated only when every participant is selected in ApplicationUnits.cs. |
| 61 | + // ToUnit() retains a strongly typed quantity for subsequent formatting or calculations. |
| 62 | + PrintSection("Estimate the driving day"); |
| 63 | + Duration drivingTime = Duration.Parse("4 h", Invariant) + Duration.FromMinutes(25); |
| 64 | + Speed averageSpeed = totalRoute / drivingTime; |
| 65 | + Speed displaySpeed = averageSpeed.ToUnit(SpeedUnit.KilometerPerHour); |
| 66 | + |
| 67 | + Console.WriteLine($"Driving time: {drivingTime.ToString("0.0", Invariant)}"); |
| 68 | + Console.WriteLine($"Average speed: {displaySpeed.ToString("0.0", Invariant)}"); |
| 69 | + |
| 70 | + // APIs illustrated: cross-quantity multiplication and its inferred division operator. |
| 71 | + // The Power * Duration = Energy relationship handles anchor-unit conversions, so multiplying |
| 72 | + // kW by minutes works directly; dividing Energy by Power yields the corresponding Duration. |
| 73 | + PrintSection("Plan the charging stop"); |
| 74 | + Power charger = Power.Parse("150 kW", Invariant); |
| 75 | + Duration chargingWindow = Duration.FromMinutes(22); |
| 76 | + Energy deliveredEnergy = charger * chargingWindow; |
| 77 | + Energy requestedEnergy = Energy.Parse("42 kWh", Invariant); |
| 78 | + Duration timeToAddRequestedEnergy = requestedEnergy / charger; |
| 79 | + Energy displayEnergy = deliveredEnergy.ToUnit(EnergyUnit.KilowattHour); |
| 80 | + Duration displayChargeTime = timeToAddRequestedEnergy.ToUnit(DurationUnit.Minute); |
| 81 | + |
| 82 | + Console.WriteLine($"Charger: {charger.ToString("0", Invariant)}"); |
| 83 | + Console.WriteLine($"Energy in 22m: {displayEnergy.ToString("0.0", Invariant)}"); |
| 84 | + Console.WriteLine($"Time for 42kWh: {displayChargeTime.ToString("0.0", Invariant)}"); |
16 | 85 |
|
17 | | - Console.WriteLine($"{route} in {elapsed} = {averageSpeed.ToUnit(SpeedUnit.KilometerPerHour)}"); |
| 86 | + // APIs illustrated: a custom JSON-defined quantity using the same generated API as a built-in. |
| 87 | + // ParcelCount comes from ParcelCount.unitsnet.json and receives parsing, construction, |
| 88 | + // conversion, formatting, and arithmetic without hand-written quantity code. |
| 89 | + PrintSection("Count the cargo with a custom quantity"); |
| 90 | + ParcelCount manifest = ParcelCount.Parse("2 doz", Invariant) + ParcelCount.FromParcels(6); |
| 91 | + ParcelCount parcelManifest = manifest.ToUnit(ParcelCountUnit.Parcel); |
| 92 | + ParcelCount dozenManifest = manifest.ToUnit(ParcelCountUnit.Dozen); |
| 93 | + Console.WriteLine( |
| 94 | + $"Manifest: {parcelManifest.ToString("0", Invariant)} " + |
| 95 | + $"({dozenManifest.ToString("0.00", Invariant)})"); |
18 | 96 |
|
19 | | - // This type is generated from GameScore.unitsnet.json in this project. |
20 | | - GameScore score = GameScore.FromDozens(2) + GameScore.FromPoints(6); |
21 | | - Console.WriteLine($"{score.ToUnit(GameScoreUnit.Point)} = {score.ToUnit(GameScoreUnit.Dozen)}"); |
| 97 | + // APIs illustrated: the legacy-shaped Quantity facade, modular registry, and UnitSystem policy. |
| 98 | + // Quantity.From(...) keeps a familiar call shape for configuration-driven code. Unlike legacy |
| 99 | + // UnitsNet, the facade is scoped to the selected module and returns IQuantity<double> from |
| 100 | + // UnitsNet.Core. The modular registry exposes that selected catalog without global mutation. |
| 101 | + PrintSection("Use the modular dynamic API"); |
| 102 | + CoreQuantity configuredDistance = Quantity.From(15, "Length", "Mile"); |
| 103 | + QuantityRegistry registry = GeneratedQuantityRegistry.Instance; |
| 104 | + IQuantityDescriptor lengthDescriptor = registry.Get("Length"); |
| 105 | + double configuredKilometers = registry.Convert(15, "Length", "Mile", "Kilometer"); |
| 106 | + |
| 107 | + string formattedConfiguredDistance = lengthDescriptor.Format(configuredDistance, "0.0", Invariant); |
| 108 | + Console.WriteLine( |
| 109 | + $"Configured leg: {formattedConfiguredDistance} = " + |
| 110 | + $"{configuredKilometers.ToString("F1", Invariant)} km"); |
| 111 | + Console.WriteLine($"Selected types: {string.Join(", ", registry.Names.Order())}"); |
| 112 | + |
| 113 | + // Modular unit-system policy is immutable and explicit. It resolves only among units selected |
| 114 | + // by this module instead of changing process-wide UnitsNetSetup defaults at runtime. |
| 115 | + Length siRoute = totalRoute.ToUnit(UnitSystem.SI); |
| 116 | + Console.WriteLine($"SI policy: {siRoute.ToString("0", Invariant)}"); |
| 117 | + |
| 118 | + // API illustrated: the System.Text.Json converter exposed by the generated registry. |
| 119 | + // It knows the selected concrete types without assembly scanning, keeping serialization |
| 120 | + // friendly to trimming and Native AOT. |
| 121 | + PrintSection("Persist a strongly typed result"); |
| 122 | + var jsonOptions = new JsonSerializerOptions { WriteIndented = false }; |
| 123 | + jsonOptions.Converters.Add(GeneratedQuantityRegistry.JsonConverter); |
| 124 | + string json = JsonSerializer.Serialize(displayEnergy, jsonOptions); |
| 125 | + Energy restoredEnergy = JsonSerializer.Deserialize<Energy>(json, jsonOptions); |
| 126 | + |
| 127 | + Console.WriteLine($"JSON: {json}"); |
| 128 | + Console.WriteLine($"Round trip:{restoredEnergy.ToString("0.0", Invariant),10}"); |
22 | 129 |
|
23 | 130 | Console.WriteLine(); |
24 | | - Console.WriteLine("Try editing ApplicationUnits.cs, GameScore.unitsnet.json, or this file, then run:"); |
| 131 | + Console.WriteLine("Try editing ApplicationUnits.cs, ParcelCount.unitsnet.json, or this file, then run:"); |
25 | 132 | Console.WriteLine(" dotnet run"); |
26 | 133 | } |
| 134 | + |
| 135 | + private static string FormatStoredValue(Length length) => length.ToString("0.#", Invariant); |
| 136 | + |
| 137 | + private static void PrintSection(string title) |
| 138 | + { |
| 139 | + Console.WriteLine(); |
| 140 | + Console.WriteLine(title); |
| 141 | + Console.WriteLine(new string('-', title.Length)); |
| 142 | + } |
27 | 143 | } |
0 commit comments