TinyCLR OS 3.0 Survey

Shape the future of .NET and C# on embedded systems!

Did you know that TinyCLR OS can be used to build reliable embedded products using your favorite tools, .NET and C#?

We’re preparing for our next major TinyCLR OS 3.0 release, and your feedback is crucial to help us prioritize new features and improvements. Please take a moment to fill out our survey and let us know what you want to see next.

button

1 Like

Just to share that these have already been added::

internal sealed class RefBox<T> where T : class
    {
        public T Payload;

        public RefBox(T value)
        {
            Payload = value;
        }

        public T GetPayload()
        {
            return Payload;
        }
    }

    /// <summary>Closed constructed generic on a reference type (GENERICINST in metadata).</summary>
    internal sealed class GenericHolder
    {
        public RefBox<string> BoxField;

        public GenericHolder(string s)
        {
            BoxField = new RefBox<string>(s);
        }
    }

    /// <summary>Two-arity generic (Pair`2). Exercises declaring token on GENERICINST with two args.</summary>
    internal struct Pair<TU, TV>
    {
        public TU U;
        public TV V;

        public Pair(TU u, TV v)
        {
            U = u;
            V = v;
        }

        /// <summary>Instance method on constructed Pair; exercises MethodRef parent = TypeSpec.</summary>
        public string Describe()
        {
            return U.ToString() + "/" + V.ToString();
        }
    }

    /// <summary>Value-type constraint + instance methods on closed generic (MethodRef + FieldRef paths).</summary>
    internal sealed class ValCell<T> where T : struct
    {
        public T Cell;

        public void Write(T value)
        {
            Cell = value;
        }

        public T Read()
        {
            return Cell;
        }
    }

    internal static class Program {
        private static GenericHolder s_holder;
        static void Main() {
            var cnt = 0;
            var gpioController = GpioController.GetDefault();

            var led = gpioController.OpenPin(SC20260.GpioPin.PA8);
            led.SetDriveMode(GpioPinDriveMode.Output);
            var button = gpioController.OpenPin(SC20260.GpioPin.PB7);
            button.SetDriveMode(GpioPinDriveMode.InputPullUp);

            s_holder = new GenericHolder("generics-field-ok");
            Debug.WriteLine(s_holder.BoxField.GetPayload());

            // Local closed generic + array of constructed type
            var localBox = new RefBox<string>("generics-local-ok");
            Debug.WriteLine(localBox.GetPayload());

            var arr = new RefBox<string>[2];
            arr[0] = localBox;
            arr[1] = s_holder.BoxField;
            Debug.WriteLine(arr[0].Payload + " | " + arr[1].Payload);

            // Generic method (explicit type args); exercises generic method prefix in signatures
            var n = Identity<int>(7);
            Debug.WriteLine("Identity<int>: " + n.ToString());

            var s = Identity<string>("method-generic-ok");
            Debug.WriteLine(s);

            // Pair<int,int>: arity-2 GENERICINST; Describe() -> MethodRef on constructed type
            var pair = new Pair<int, int>(40, 2);
            Debug.WriteLine("Pair<int,int>: " + pair.Describe());

            // ValCell<int>: struct type arg + Write/Read -> MethodRef on ValCell`1<int>
            var cell = new ValCell<int>();
            cell.Write(12345);
            Debug.WriteLine("ValCell<int>.Read: " + cell.Read().ToString());

            while (true) {
                Debug.WriteLine("Hello from TinyCLR7.9! Count: " + cnt++);
                Thread.Sleep(250);
                led.Write(led.Read() == GpioPinValue.Low ? GpioPinValue.High : GpioPinValue.Low);

                if (button.Read() == GpioPinValue.Low)
                {
                    DoTest();                    
                }                
            }
        }

        static int counter = 0;
        static void DoTest()
        {
            counter++;
            Debug.WriteLine($"Do Test jump to function {counter}");
        }

        private static T Identity<T>(T value)
        {
            return value;
        }
    }
namespace TinyCLRApp10_Task2 {
    class Program {
        static async Task Main() {
            Debug.WriteLine("=== TinyCLR Async/Await Test ===");

            // 1. Basic await of a Task that does real work + delay
            Debug.WriteLine("[1] Start");
            await DoWorkAsync();
            Debug.WriteLine("[1] End");

            // 2. Task<T> with a return value
            Debug.WriteLine("[2] Computing...");
            var answer = await ComputeAsync(20, 22);
            Debug.WriteLine("[2] 20 + 22 = " + answer.ToString());

            // 3. Task.FromResult - already-completed task
            var preset = Task.FromResult(100);
            var val = await preset;
            Debug.WriteLine("[3] FromResult = " + val.ToString());

            // 4. Sequential awaits - no parallelism expected, each runs fully
            Debug.WriteLine("[4] Sequential 3 x 500 ms...");
            var startTicks = DateTime.UtcNow.Ticks;
            await Task.Delay(500);
            await Task.Delay(500);
            await Task.Delay(500);
            var elapsedMs = (DateTime.UtcNow.Ticks - startTicks) / TimeSpan.TicksPerMillisecond;
            Debug.WriteLine("[4] Elapsed ~" + elapsedMs.ToString() + " ms (expect ~1500)");

            // 5. Nested async calls
            Debug.WriteLine("[5] Nested...");
            await OuterAsync();
            Debug.WriteLine("[5] Back in Main");

            // 6. Mixed sync work between awaits
            Debug.WriteLine("[6] Counting...");
            for (var i = 1; i <= 3; i++) {
                await Task.Delay(300);
                Debug.WriteLine("[6] tick " + i.ToString());
            }

            Debug.WriteLine("=== All tests passed ===");

            var cnt = 0;
            while (true) {
                Debug.WriteLine("alive " + (cnt++).ToString());
                Thread.Sleep(1000);
            }
        }

        static async Task DoWorkAsync() {
            Debug.WriteLine("    Working...");
            await Task.Delay(2000);
            Debug.WriteLine("    Done");
        }

        static async Task<int> ComputeAsync(int a, int b) {
            await Task.Delay(200);
            return a + b;
        }

        static async Task OuterAsync() {
            Debug.WriteLine("    Outer begin");
            await InnerAsync();
            Debug.WriteLine("    Outer end");
        }

        static async Task InnerAsync() {
            Debug.WriteLine("      Inner begin");
            await Task.Delay(300);
            Debug.WriteLine("      Inner end");
        }
    }
}

namespace TinyCLRApp10_List {
    
    class Program {
    

        static void Main()
        {
            var list = new System.Collections.Generic.List<int>();
            list.Add(1);
            list.Add(2);
            list.Add(3);
            Debug.WriteLine(list.Count.ToString()); // 3

            foreach (var item in list)
                Debug.WriteLine(item.ToString()); // 1, 2, 3

   
            list.Remove(2);
            Debug.WriteLine(list.Count.ToString()); // 2
            var cnt = 0;
            while (true) {
                Debug.WriteLine("Hello from TinyCLR! Count: " + cnt++);
                Thread.Sleep(1000);
            }
        }
    }
}
namespace TinyCLRApp10_Tuble {
    class Program {
        static void Main() {
            Console.WriteLine("=== Tuple test ===");

            // 1. Basic 2-tuple via factory
            var pair = Tuple.Create(42, "hello");
            Debug.WriteLine("[1] Item1 = " + pair.Item1.ToString());
            Debug.WriteLine("[1] Item2 = " + pair.Item2);
            Debug.WriteLine("[1] ToString = " + pair.ToString());   // (42, hello)

            // 2. 3-tuple of ints
            var coords = Tuple.Create(10, 20, 30);
            Debug.WriteLine("[2] coords = " + coords.ToString());   // (10, 20, 30)
            Debug.WriteLine("[2] sum = " + (coords.Item1 + coords.Item2 + coords.Item3).ToString());

            // 3. Value-based equality
            var a = Tuple.Create(1, 2);
            var b = Tuple.Create(1, 2);
            var c = Tuple.Create(1, 3);
            Debug.WriteLine("[3] a.Equals(b) = " + a.Equals(b).ToString());   // True
            Debug.WriteLine("[3] a.Equals(c) = " + a.Equals(c).ToString());   // False
            Debug.WriteLine("[3] ReferenceEquals(a,b) = " + object.ReferenceEquals(a, b).ToString());   // False

            // 4. Hash codes: equal tuples -> same hash
            Debug.WriteLine("[4] a.GetHashCode == b.GetHashCode : "
                + (a.GetHashCode() == b.GetHashCode()).ToString());   // True

            // 5. Tuple as a return value (the classic use case)
            var result = Divide(17, 5);
            Debug.WriteLine("[5] 17 / 5 = " + result.Item1.ToString() + " remainder " + result.Item2.ToString());

            // 6. Nullable / mixed types (5-arity)
            var t5 = new Tuple<int, string, bool, int, string>(1, "two", true, 4, "five");
            Debug.WriteLine("[6] t5 = " + t5.ToString());
            Debug.WriteLine("[6] t5.Item3 = " + t5.Item3.ToString());

            // 7. Tuple with null element
            var withNull = Tuple.Create<string, string>("A", null);
            Debug.WriteLine("[7] withNull = " + withNull.ToString());   // (A, )

            Debug.WriteLine("=== Done ===");

            var cnt = 0;
            while (true) {
                Debug.WriteLine("alive " + (cnt++).ToString());
                Thread.Sleep(1000);
            }
        }

        // Classic pattern: return two values without an out parameter
        static Tuple<int, int> Divide(int a, int b) => Tuple.Create(a / b, a % b);
    }
}

This is not a promise yet, as it depends on last-minute approval, but it has already been added and is running on TinyCLR.

5 Likes

This is what Claude thinks after reading your post:

. . .This is actually a pretty big deal for TinyCLR. . .

Dat_Tran explicitly says “This is not a promise yet, as it depends on last-minute approval, but it has already been added and is running on TinyCLR.” ghielectronics So: running in the lab, not confirmed for 3.0 release. Worth watching the thread.

Also worth noting: “generics work in these samples” doesn’t automatically mean the full BCL generic surface is there. Dictionary<TKey, TValue>, HashSet<T>, LINQ — any of those could still be missing. And async doesn’t automatically mean ConfigureAwait, Task.WhenAll, cancellation tokens, etc. are all wired up. The samples show what’s been demonstrated, not necessarily the full surface.

Bottom line

If this lands in 3.0, TinyCLR becomes a meaningfully different platform — much closer to “small .NET” than “embedded-flavored .NET.”

:winking_face_with_tongue:

1 Like

Full .NET is huge. We aim to get as close as possible, but not everything is implemented. We also need it to run on low-cost boards.

1 Like

Oh for sure, .net is huge. I just thought Claude codes review of your examples was interesting. :wink:

1 Like

If we’re still taking requests, It would be nice to be able to create the .tca deployment files without needing a working board connected.

This feature has already been added. Please let me know what you’re not satisfied with.

1 Like

Wait a minute, when did you add that?

Ok. How do I set the key?

create a file name tca.json same folder to your .csproj

file look like:

{

“AppKey”: “00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00”,

“AppVersion”: “1.0.0.0”

}

Replace by your key and version. The extension reads that file and do their task.

Wait a minute, when did you add that?

Long time ago :d

1 Like