@ysharp_design/

SE 328643

C#

No description

fork
loading
Files
  • main.cs
main.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SE
{
    /// <summary>
    /// SE 328643:
    /// List of any object T
    /// http://programmers.stackexchange.com/questions/328643/list-of-any-object-t
    /// </summary>
    public interface IDomainEvent { }

    public interface IHandler<T> where T : IDomainEvent
    {
        // Btw, why this T return type (instead of void) if we ignore it in Bus<T>.Raise(...) anyway?
        T Handle(T eventData);
    }

    public class Bus<T> where T : IDomainEvent
    {
        private static readonly ConcurrentBag<IHandler<T>> handlers = new ConcurrentBag<IHandler<T>>();

        /// <summary>
        /// Register a IHandler<T> handler for a Bus<T>
        /// (assuming the latter has a BusDispatch<T>, itself, already registered)
        /// </summary>
        public static void RegisterHandler<H>(H handler) where H : IHandler<T>
        {
            handlers.Add(handler);
        }

        /// <summary>
        /// Invoke all the registered IHandler<T>'s (if any) for the given event data, typed T
        /// </summary>
        public static void Raise(T eventData)
        {
            // Make sure the current thread is going to enumerate a frozen collection:
            var handlers = Bus<T>.handlers.Reverse().ToArray();
            // Now, enumerate:
            foreach (var handler in handlers)
            {
                handler.Handle(eventData);
            }
        }
    }

    public delegate void BusDispatch<T>(T eventData) where T : IDomainEvent;

    public class AnyBus
    {
        private static readonly ConcurrentDictionary<Type, Delegate> busDispatch =
            new ConcurrentDictionary<Type, Delegate>
            (
                new[]
                {
                    // Let AnyBus know about a few hard-wired (aka "builtin") (BusDispatch<?>) Raise delegates
                    new KeyValuePair<Type, Delegate>(typeof(Bus<Person>), (BusDispatch<Person>)Bus<Person>.Raise),
                    new KeyValuePair<Type, Delegate>(typeof(Bus<Thing>), (BusDispatch<Thing>)Bus<Thing>.Raise)
                }
            );

        private static Type GetBusType<T>() where T : IDomainEvent
        {
            return typeof(Bus<>).MakeGenericType(typeof(T));
        }

        private static void UnsupportedEventDataError(Unsupported unsupported)
        {
            var eventData = unsupported.WrappedEventData;
            throw new InvalidOperationException(string.Format("Unsupported event data: {0}", (eventData != null ? eventData.GetType() : typeof(void)).Name));
        }

        internal class Unsupported : IDomainEvent
        {
            internal object WrappedEventData { get; set; }
        }

        /// <summary>
        /// Register a (BusDispatch<T>) Raise delegate for a given event data type T,
        /// or keep whichever existing one
        /// </summary>
        public static void RegisterDispatch<T>() where T : IDomainEvent
        {
            Func<Type, Delegate, Delegate> keepExisting = (type, @delegate) => @delegate;
            var busType = GetBusType<T>();
            busDispatch.AddOrUpdate(GetBusType<T>(), (BusDispatch<T>)Bus<T>.Raise, keepExisting);
        }

        public static void Raise<T>(T eventData) where T : IDomainEvent
        {
            var busType = GetBusType<T>();
            // We choose to use TryAdd(... UnsupportedEventData) as we want to be liberal enough
            // if it just so happens that another thread has just registered the (BusDispatch<T>) Raise we're precisely
            // interested in (for the provided event data, typed T);
            // however, we probably still *do* want to inform the application if it forgot to use,
            // AnyBus.RegisterDispatch<T>() -- throughout *all* threads -- prior to calling AnyBus.Raise
            busDispatch.TryAdd(busType, (BusDispatch<Unsupported>)UnsupportedEventDataError);
            if (!(busDispatch[busType] is BusDispatch<Unsupported>))
            {
                busDispatch[busType].DynamicInvoke(eventData);
            }
            else
            {
                UnsupportedEventDataError(new Unsupported { WrappedEventData = eventData });
            }
        }
    }

    public class SomeBase
    {
        public override string ToString() { return Data != null ? Data.ToString() : string.Empty; }
        public object Data { get; set; }
    }

    public class Person : SomeBase, IDomainEvent { }

    public class Phase1PersonHandler : IHandler<Person>
    {
        public Person Handle(Person person)
        {
            Console.WriteLine("Phase #1 {0}...", person);
            return person;
        }
    }

    public class Phase2PersonHandler : IHandler<Person>
    {
        public Person Handle(Person person)
        {
            Console.WriteLine("Phase #2 {0}...", person);
            return person;
        }
    }

    public class Thing : SomeBase, IDomainEvent { }

    public class ThingHandler : IHandler<Thing>
    {
        public Thing Handle(Thing thing)
        {
            Console.WriteLine("Handling {0}...", thing);
            return thing;
        }
    }

    public class SomethingElse : SomeBase, IDomainEvent { }

    public class SomethingElseHandler : IHandler<SomethingElse>
    {
        public SomethingElse Handle(SomethingElse somethingElse)
        {
            Console.WriteLine("Also handling {0}...", somethingElse);
            return somethingElse;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // AnyBus already knows about how to dispatch the Person and Thing domain event
            // (no need to call AnyBus.Register<?>() for those privileged ones)
            Bus<Person>.RegisterHandler(new Phase1PersonHandler());
            Bus<Person>.RegisterHandler(new Phase2PersonHandler());
            Bus<Thing>.RegisterHandler(new ThingHandler());

            // Now, pretend we forgot to write the following two lines -- especially the first one:
            //AnyBus.RegisterDispatch<SomethingElse>();
            //Bus<SomethingElse>.RegisterHandler(new SomethingElseHandler());

            try
            {
                AnyBus.Raise(new Person { Data = "Person 1" });
                AnyBus.Raise(new Thing { Data = "Thing 1" });
                AnyBus.Raise(new Person { Data = "Person 2" });
                AnyBus.Raise(new SomethingElse { Data = "Something else" });
            }
            catch (Exception ex)
            {
                Console.WriteLine("Ouch:\r\n{0}", ex.Message);
            }

            //Console.ReadKey();
        }
    }
}