Click here to Skip to main content
15,881,455 members
Articles / Programming Languages / C#
Article

Complete Managed Media Aggregation - Part I: Designing a Class vs Designing a Framework

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
9 Apr 2013CPOL15 min read 16.8K   208   6  
Designing a class vs designing a framework

Introduction

Before I begin, I would like to state that the code presented is depicted in a manner which is only consistent for the purpose of this article and the subject matter herein.

These concepts are not considered complete or finalized. They are just banter in the development of my library Managed Media Aggregation which is undergoing a huge rewrite at the current time to support additional middle tier protocol such as Transport Stream inter alia.

While performing this rewrite, I wanted to take the time to take note of how easy it seems to be to lose focus on the Core Principles of a project.

Take for instance Windows 8, you either love it or hate it; and believe it or not Microsoft actually had a goal when they set out to create Windows 8, whether the goal is chasing users away or drawing users in is up for debate.

This goes into effect with a majority of what we would call breaking changes are employed.

Users really wouldn’t care when their Operating System changed or what it looked like just so long as everything which they could do before was able to be done after some upgrade or update.

If Windows XP didn’t contain some type of compatibility mode, it would have never worked...

Fast forward a decade plus later and we have Windows 8, which still has a compatibility feature. Believe it or not, though this compatibility feature works less often than not in situations you may come to expect differently from other versions of Windows < 8.

This is ironic in my opinion considering this was one of the largest driving factors behind the adoption of Windows XP, you would think that after all the changes they planned; all the time in testing and development they spent... (assuming they didn’t coin the type “Micro$oft” and then let everyone use it just so Billy could laugh a few times at everyone else's expense..) That they would have just “F**7*(6” have ensured the compatibility feature in some way which allowed users to go to a Start Menu esque environment where they had access those applications and features... Take Magnifier... it runs on the Desktop... where you can’t do ANYTHING useful with it... How would that be useful to someone who relies on the Magnifier?

Not that I rely on the Magnifier or that this article is related to Windows or Linux or any other Operating System but we have the point hopefully which I will state for the record is that “Change seems to only affect people if they notice it”.

Now let’s look at that concept for a moment, can that really be true?

This is also true in more than technology and just about everything except Math and Physics.

There are a few really good analogies for this, however I wanted to make up my own and so here it goes:

Windows 3.1 => Windows 3.11 was a minimal change at best to most people in general (e.g. techies aside)
3.11 => Windows 95 Huge Impact for everyone... On the news, lines at stores you name it!
Windows 95 (version 4.0)
Windows 98 and Windows 98 SE (version 4.1)
Windows ME (version 4.9)

Not so much hype for a while.

Windows NT 3.1
Windows NT 3.5
Windows NT 3.51
Windows NT 4.0
Windows 2000 (NT version 5.0)

No SO much more Hype... Then a HUGE IMPACT.

Windows XP and Windows Fundamentals for Legacy PCs (NT version 5.1)
Windows Server 2003 and Windows XP Professional x64 Edition (NT version 5.2)
Windows Vista and Windows Server 2008 (NT version 6.0)
Windows 7 and Windows Server 2008 R2 (NT version 6.1)
Windows 8 and Windows Server 2012 (NT version 6.2)

No one cared about all the interim stuff that was happening until something they could see affected them, infact even if they liked what they could see but one of their precious User Mode DOS programs wouldn’t run then they would be angry because they would be forced to reboot to Protected Mode which limited memory access inter alia.

But how is any of this related to this article?

Well for starts, Microsoft does the same thing they do with Windows that they do with the .NET Framework... They start something and get so far and they refine and refactor and then BOOM something out of left field... async... It’s not Linq and it doesn’t help me and I [probably] will never use it however I am 100% aware that people love it especially U.I. Programmers who don’t really understand the more complex processes involved with the mechanics and just rely on the fact when they use ‘await’ they know the U.I. will be responsive! You can even probably hear them tout it a few times; “I used await which made everything asynchronous” or “The U.I. can’t be blocked I used await” and should be corrected immediately to ensure they are not just doing something for the sake of doing it.

You can blame Microsoft for taking away the Desktop but you can’t blame Microsoft for taking it away because of what you had on it so to type.

e.g. If you data was still in “C:\Users\Default\Desktop” after the Windows 8 update or another such update or program installation but it didn’t show up on the Desktop, is Microsoft to blame or the person who ran the program?

That is also not a debate I wish to start so let's see how this is going to relate to the rest of the article...

As computers sciences, it is our responsibility to always improve and invent, it is some of the core ethics involved with the field; to make something more efficient or less expensive or easier to type on a keyboard or manufacture in a production line.

Understanding this, I think we all got really satisfied by Extension methods and kind of left well enough alone, then the DLR came and so on and so forth but where are the user developed concepts to encapsulate old design patterns and simplify them? That is not the job of Microsoft, is it? Just as it could be theirs and vice versa for both sides of the argument; new technology and old, community developed or otherwise, the problem is the same.

We must always ensure we are doing this to the best of our abilities and all the while trying to improve not only our code but our understanding of the processes involved with executing and maintaining said code which means computers in general as well as not only the lines of code but the bigger picture which is how to write less code which does more and to know how it works and why it works.

That is what this article is about; Knowing your code, knowing how it works and what parts are similar and not...

When to take a Situation of ‘It m_This, m_That’ properties and to turn them into an ‘EncapsulatedThey’ which is IEnumerable<it>.

That example is probably the worst I have given yet and conveniently at the most crucial point which is where I want you to read more so hopefully you can get past that because there will be plenty more examples; Just remember the purpose is to know your code and what it is doing and to learn to have the ability to say: “I Built this machine X in code, how can I take X and turn it into a Z which has A,B and C and encapsulates them completely and efficiently and give myself as well as someone else the ability to do more with less”.

Designing or maintaining a class is something most any developer has done, if you think you have not developed a class and you program in .NET, then I encourage you to refine your understanding of your work on a day to day basis.

More frequently, we developers find ourselves maintaining a class rather than creating a new class due to various reasons such as a requirements change.

It is during these times that we sometimes look at our classes that are being maintained and we strive to improve them so change requirements do not affect them as severely and possibly also to give them more flexibility.

Sometimes even when a class is working completely as expected and serving its purpose; we as computer sciences strive to reduce the overhead of the class and make it as efficient as possible.

We also try to break down functionality and separate the rules and responsibilities required to build on the work already done in existing classes and allow new work to be performed.

Most of the time, when this occurs it may be outside the scope of the intentions or responsibilities of the class being analyzed.

It is at this time your class usually becomes a framework.

What ends up happening is that a bunch of seemingly unrelated classes get condensed into a single pattern where they are all heterogeneously accessible from the core namespace and concepts provided in the initial classes.

This describes a framework... a bunch of classes working together to allow more functionality than they would on their own or in other words, without the Framework underpinnings to allow them to have the insight into each other to act as a single coherent unit.

As an example of this is having a class with a byte[] as a buffer and various methods which interact with said buffer.

(Custom pseudo notation is used)

C#
Let class Test =  { 
byte[] m_Buffer; 
void SetBuffer(byte[]);
int GetBufferLength();
byte GetByte(int index);
void SetByte(int index, byte value);
};

Let's say the program started out using a bunch of these “Test” classes in some capacity.

During the course of the use of the program, it was determined that someone would like to add the ability to offset the data in the m_Buffer by an arbitrary index.

We can quickly visualize the change required:

C#
Let class Test =  { 
byte[] m_Buffer; 
int m_Offset;
public void SetBuffer(byte[]);
public int GetBufferLength();
public byte GetByte(int index);
public void SetByte(int index, byte value);
public int GetOffset();
public void SetOffset(int);
};

However, it would be just as valid to solve this problem in a different way... via subclassing.

C#
Let class OffsetTest := Test { 
int m_Offset;
public int GetOffset();
public void SetOffset(int);
};

Yet it would also be possible to solve this problem in yet another way and is what some programmers do....

C#
Let class TestEx =  { 
Test m_Test;
int m_Offset;
public int GetOffset();
public void SetOffset(int);
};
And yet another way...

Let class TestManager =  { 
Dictionary<test,int /> m_Tests;
public void AddTest(Test, int);
public int TestOffset(Test);
};

And I am sure I could go on and on with design possibilities all of which have their own ups and downs and is not the focus of this article.

Now that the example is clearly defined and hopefully understood, we proceed further to discuss the best practices involved and give a concrete example of how this type of development typically unrolls.

Typically when you start to design a class, you have different goals than when you set out to design a framework or API.

A class has implementation specific processes, which can mean private, or protected members whereas an API or Framework usually has public members.

A Framework can also have internal and protected members, however they are not visible to the consumers of the library without the use of reflection and thus beyond the scope of this article.

When designing a class, do not try and design a Framework, build your class, work with your class, maintain your class and then and only then build a Framework around your class to make it more efficient and more usable.

During your use, you may have encountered scenarios where it seems that your class needed special code to handle a corner case or an additional check when performing a certain function.

Take note of these quirks, employ standard exception logging practices and ensure that you are aware of these metrics even when you don’t use your class frequently....

You don’t want someone like me walking over to you talking about Sons Of Strike and nested exceptions on the stack causing a OOM’s when all you really needed to do was keep a few Application.OnUnhandledException handlers in your unit test code (if you even had any unit test code)

Your goal in doing all of this is to insight others to use your framework and ideas and you don’t want them finding bugs you didn’t know about in the first place because that usually indicates you don’t know what you're doing or that you were careless in your work.

It pays to have a known issues area where you can document this type of activity as well as goals to eliminate it.

Let's take for instance a common design pattern for loops e.g. for(), (do)while(), etc.

.NET provides an abstraction around such patterns known as Enumerables.

Enumerables work by following another design pattern IEnumerator...

I personally consider them no different than iterators in most other languages, the main difference being that in C#, you typically can’t assign through the Enumerator because of the pattern presented in its design.

This is another example of a Framework paradigm, the Enumerable uses the IEnumerator to iterate.

What if you wanted to programmatically continually loop the Enumerable from beginning to end without having the code to check if the IEnumerator MoveNext() failed, then call Reset(), etc.

You would have to design a class...

This is interesting to me because the subject matter is simple, yet there is seemingly no way to write “while(true);” in Linq or using an Enumerable...

Let's take this quirk as a challenge to provide a class which has a purpose which is to provide the subject matter which is perpetuating a generated IEnumerator while the enumerable exists.

We are designing a class but this class also has to fit into the design patterns provided by the Framework.

We will use this opportunity to take note of any quirks we find while using the class as well as to see if we can find dualities in its operation which make it a candidate to be broken down into smaller subsets which can then become their own module within the framework.

... But wait, you say...

If you think that this is something easily solved in code and does not require a class and should possibly even NOT be a class, I can already think of a few things to rebut:

It will have a lot more uses than initially conceived; while it may not be that useful or practical to say while(true) and thus a AsPerpetual(IEnumerable) would not be much more useful, I challenge you to think outside the box..

C#
Let static class UselessSeries := IEnumerator<int /> {
int m_Initial = m_State = seed ?? -1, m_Min = min ?? int.MinValue, m_Max = max ?? int.MaxValue;	
UselessSeries (int? min = null, int? max = null, int seed? = null);
public int Next(int min = 0, int max = 0){
	if(min == max) return min;
	else if(min > max) Swap(ref min, ref max);
if(seed.HasValue) m_State = seed;	
	return Math.Sqrt(Math.Min(max,Math.Max(seed, min)))
}
void Swap(ref int a, ref int b) { a^=b; b^=a; a^=b; }
int IEnumerator<int />.Current { get { return Next(m_Min, Next(m_State, m_Max)); } }
bool IEnumerator.HasNext { get { return true;} }
void Reset() { m_State = m_Initial; }
};

Also not very useful... however if you combine the two together you get something which is semi useful depending on what you are doing however only by keeping the classes out of separate need and then testing for did we find these dualities which allow for previously unused combinatorics.

Imagine now calling UsefulSeries.AsPerpetual() where your series is defined in a way which it is doing something such as monotonically increasing a value.

You may then see that other things can be added to the new PerpetualEnumerable such as events and exceptions...

This allows the concept to be taken even further, e.g., to allow you to always reflect a series of values; then catch certain types of exceptions programmatically to take either a brake or a continue or a change in direction over the iteration. Other exception types could also be envisioned such as ones for signaling...

Imagine a RerfeshingEnumerable<t /> which derives from PerpetualEnumerable<t /> which iterates over a given IEnumerable<t /> and instead of stopping enumeration, it will throw a break Exception which will be observed by the RerfeshingEnumerable and will cause it to take another snapshot of the underlying collection....

And now enough with the high level examples because quickly one can see how this is turning into more than a rebuttal and more like a rant...

Let's look at some code!

C#
#region PerpetuatingEnumerable

    /// <summary>
    /// Encapsulates the IEnumerator by always returning from the beginning. 
    /// If nothing is contained you will still be able to loop.
    /// </summary>
    /// <typeparam name="T">The type of the items contained in the 
    /// PerpetuatingEnumerable</typeparam>
    /// <remarks>
    /// When past the end of the initial Enumerator Perpetual will be set to true.
    /// </remarks>
    public class PerpetuatingEnumerable<T> : IEnumerator<T>, 
    IEnumerable<T>, System.Collections.IEnumerator, System.Collections.IEnumerable
    {
        #region Nested Types

        public class ExceptionInstruction : Exception { }

        public class BreakInstruction : ExceptionInstruction { }

        public class ContinueInstruction : ExceptionInstruction { }

        public delegate void PerpetuatingEvent(PerpetuatingEnumerable<T> sender, T state);

        #endregion

        public event PerpetuatingEvent Perpetuated, Completed;

        IEnumerable<T> enumerable;

        IEnumerator<T> enumerator;

        internal protected void OnPerpetuated(PerpetuatingEnumerable<T> sender, T item) 
        { sender.Perpetuated -= OnPerpetuated; sender.Perpetual = true; }
        internal protected void OnCompleted(PerpetuatingEnumerable<T> sender, T item) 
        { sender.Completed -= OnCompleted; }

        public bool Perpetual { get; private set; }

        public T Current
        {
            get
            {
                if (enumerator == null) enumerator = enumerable.AsParallel().GetEnumerator();
                return enumerator.Current;
            }
        }

        public PerpetuatingEnumerable(IEnumerable<T> toLoop)
        {
            if (toLoop == null) throw new ArgumentNullException();
            enumerable = toLoop;
            enumerator = enumerable.GetEnumerator();
            Perpetuated += OnPerpetuated;
            Completed += OnCompleted;
        }

        public PerpetuatingEnumerable(T item) : this(item.Yield()) { }

        public PerpetuatingEnumerable(System.Collections.IEnumerable toLoop) : 
        		this((IEnumerable<T>)toLoop) { }

        public void Dispose()
        {
            Completed(this, Current);
            enumerator.Dispose();
            enumerator = null;
            enumerable = null;
        }

        public int Count { get { unchecked { return (int)enumerable.Count(); } } }

        object System.Collections.IEnumerator.Current { get { return enumerator.Current; } }

        public bool MoveNext()
        {
            try
            {
                if (enumerator.MoveNext())
                {
                    return true;
                }
                else if (enumerable != null)
                {
                    try { enumerator = enumerable.GetEnumerator(); 
                    	enumerator.MoveNext(); return true; }
                    catch (InvalidOperationException) { return Perpetuate(); }
                    catch (BreakInstruction) { return false; }
                    catch (ContinueInstruction) { return true; }
                    catch { throw; }
                    finally { Perpetuated(this, Current); }
                }
                return false;
            }
            catch (BreakInstruction) { return false; }
            catch (ContinueInstruction) { return true; }
            catch (InvalidOperationException) { return Perpetuate(); }
            catch { throw; }
        }

        public void Reset() { enumerator.Reset(); }

        /// <summary>
        /// Provides a way to expand the concept without mucking with the core properties...
        /// </summary>
        /// <returns><see cref="PerpetuatingEnumerable.MoveNext"/></returns>
        public virtual bool Perpetuate() { return MoveNext(); }

        public IEnumerator<T> GetEnumerator() { return this; }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
        { return (System.Collections.IEnumerator)GetEnumerator(); }
    }


	    // Tested with the following code (Using long notation (No Extensions))

            var validStates = ((IEnumerable<StateMachineState>)Enum.GetValues
            (typeof(StateMachineState))).Where(s => s != StateMachineState.Invalid);

            var currentState = StateMachineState.Start;

            var perpetual = new Media.Common.PerpetuatingEnumerable
				<StateMachineState>(currentState);

            System.Threading.Tasks.Task simpleTask = 
            new System.Threading.Tasks.Task(() => perpetual.Any
				(run => !validStates.Contains(run)));

            simpleTask.Start();

            int i = 0;

            perpetual.Perpetuated += (p, s) =>
            {
                if (s == StateMachineState.Invalid) 
                throw new Media.Common.PerpetuatingEnumerable
			<StateMachineState>.BreakInstruction();
                Console.Write
		("----------------------------" + ++i + "----------------------------");
            };

            while (!simpleTask.IsCompleted)
            {
                Console.WriteLine("Looping" + currentState);
                currentState++;
            }

            Console.WriteLine("Not Looping" + currentState);

    #endregion 

As you can see here, nothing too fancy, nothing too crazy but extremely useful in certain situations...

Put this concept on hold for a second while I introduce another easy to comprehend wrapper which does something a lot more people may find immediately useful and then I will code with a final demonstration of how those are then tied together to create something new and inventive!

C#
#region EnumerableByteStream

    public class EnumerableByteStream : IEnumerable<byte>, IEnumerator<byte>, IList<byte>
    {
        protected System.IO.Stream m_Stream;

        public long Postition { get { return m_Stream.Position; } }

        public EnumerableByteStream(System.IO.Stream stream)
        {
            if (stream == null) throw new ArgumentNullException();
            else if (!stream.CanRead) throw new InvalidOperationException
            ("The given stream must be able to read");
            m_Stream = stream;
        }

        public IEnumerable<byte> 
        	ToArray(int offset, int count, byte[] buffer = null)
        {
            if (count == 0) return Enumerable.Empty<byte>();
            buffer = buffer ?? new byte[count];
            int len = count;
            while ((len -= m_Stream.Read(buffer, offset, count)) > 0) 
            System.Threading.Thread.Yield();
            return buffer;
        }

        public IEnumerator<byte> GetEnumerator()
        {
            int stop = -1;
            byte cache = 0;
            while (m_Stream.CanRead && (stop = m_Stream.ReadByte()) != -1) 
            	yield return cache = (byte)stop;
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
        { return GetEnumerator(); }

        internal protected int CoreIndexOf(byte item, int start, int count)
        {
            using (IEnumerator<byte> pointer = GetEnumerator())
            {
                int i = start;
                while (pointer.MoveNext() && start < count)
                {
                    if (this[i] == item) return i;
                    ++start;
                }
            }
            return -1;
        }

        public virtual int IndexOf(byte item)
        {
            return CoreIndexOf(item, 0, Count);
        }

        public virtual void Insert(int index, byte item)
        {
            if (m_Stream.CanSeek)
                if (m_Stream.CanWrite)
                {
                    m_Stream.Seek(index, System.IO.SeekOrigin.Begin);
                    m_Stream.Write(item.Yield().ToArray(), 0, 1);
                }                    
        }

        public virtual void RemoveAt(int index)
        {
            return;//Read one byte?
        }

        /// <summary>
        /// Sets or Retrieves a byte from the underlying stream
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public virtual byte this[int index]
        {
            get
            {
                if (m_Stream.CanSeek && index != m_Stream.Position){
                    m_Stream.Seek(index, System.IO.SeekOrigin.Current);
                }
                else throw new InvalidOperationException
                ("You can only move the index if the underlying stream support CanSeek");
                using (IEnumerator<byte> pointer = GetEnumerator())
                {
                    while (--index >= 0 && pointer.MoveNext()) { }
                    return pointer.Current;
                }
            }
            set
            {
                if (m_Stream.CanWrite && m_Stream.CanSeek && index != m_Stream.Position) 
                m_Stream.Seek(index, System.IO.SeekOrigin.Current);
                else throw new InvalidOperationException
                ("You can logically set a byte in the stream the index if the 
                underlying stream supports CanWrite and CanSeek");
                m_Stream.Write(value.Yield().ToArray(), 0, 1);
            }
        }

        public virtual void Add(byte item)
        {
            if (m_Stream.CanWrite) m_Stream.Write(item.Yield().ToArray(), 0, 1);
            else throw new InvalidOperationException("You can logically set 
            a byte in the stream the index if the underlying stream supports CanWrite");
        }

        public virtual void Clear()
        {
            return;
        }

        public virtual bool Contains(byte item)
        {
            //See CachingEnumerableByteStream on why not < -1
            return CoreIndexOf(item, 0, 1) != -1;
        }

        /// <summary>
        /// Advanced the underlying System.IO.Stream by reading into the given array
        /// </summary>
        /// <param name="array">The array to read into</param>
        /// <param name="arrayIndex">
        /// The index into the given array to stary copying at</param>
        public virtual void CopyTo(byte[] array, int arrayIndex)
        {
            CoreCopyTo(array, arrayIndex);
        }

        public virtual void CoreCopyTo(byte[] array, int arrayIndex, int length = -1)
        {
            if (length <= 0) return;
            if (length == -1) length = array.Length - arrayIndex;
            else if (length > m_Stream.Length) throw new ArgumentOutOfRangeException
            ("Can't copy more bytes then are available from the stream");
            m_Stream.Read(array, arrayIndex, length);
        }

        public virtual int Count
        {
            get { return (int)m_Stream.Length; }
        }

        public virtual bool IsReadOnly
        {
            get { return m_Stream.CanWrite; }
        }

        public virtual bool Remove(byte item)
        {
            //throw new NotImplementedException("Removing from a stream is not logical"); 
            //Skip e.g. Read or Discard a byte??
            return false;
        }

        public static implicit operator System.IO.Stream(EnumerableByteStream eByteStream) 
        { return eByteStream.m_Stream; }

        byte IEnumerator<byte>.Current
        {
            get { return GetEnumerator().Current; }
        }

        void IDisposable.Dispose()
        {
            return;
        }

        object System.Collections.IEnumerator.Current
        {
            get { return GetEnumerator().Current; }
        }

        bool System.Collections.IEnumerator.MoveNext()
        {
            return GetEnumerator().MoveNext();
        }

        void System.Collections.IEnumerator.Reset()
        {
            if (m_Stream.CanSeek) m_Stream.Seek(0, System.IO.SeekOrigin.Begin);
        }
    }

            //Tested with the following code (again in long notation (No extensions))
	    System.IO.MemoryStream test = new System.IO.MemoryStream();
            test.WriteByte(0);
            test.WriteByte(1);
            test.WriteByte(2);
            test.WriteByte(3);
            test.WriteByte(4);
            test.WriteByte(5);
            test.WriteByte(6);
            test.WriteByte(7);
            test.Position = 0;

            Media.Common.EnumerableByteStream ebs = new Common.EnumerableByteStream(test);

            foreach (byte b in ebs.Take(4)) Console.WriteLine(b);

            foreach (byte b in ebs)
            {
                Console.WriteLine(b);
            }

    #endregion

This code allows someone to enumerate a Stream in a straightforward manner just like it’s an IEnumerable<byte>, it even allows reading to an array by calling the ToArray method.

Now that may not be that clever you say however you can take that another step further with:

C#
#region CachingEnumerableByteStream

    //Todo Test and Complete, use LinkedStream if required
    //Aims to be a type of constrained stream as well give the 
    //ability to cache previous read bytes which may no longer be able to be read from the stream
    public class CachingEnumerableByteStream : EnumerableByteStream
    {
        //Same as above but with a Cache for previously read bytes
        List<byte> m_ReadCache = new List<byte>(), 
        m_WriteCache = new List<byte>();

        public CachingEnumerableByteStream(System.IO.Stream stream)
            :base(stream)
        {
        }

        internal void EnsureCache(int index)
        {
            try
            {
                //Read Ahead
                if (index > m_ReadCache.Count)
                {
                    byte[] buffer = new byte[index - m_Stream.Position];
                    CopyTo(buffer, 0);
                    m_ReadCache.AddRange(buffer);
                }
            }
            catch { throw; }
        }

        public override byte this[int index]
        {
            get
            {
                EnsureCache(index);
                return m_ReadCache[index];
            }
            set
            {
                //Read Ahead
                EnsureCache(index);
                base[index] = m_ReadCache[index] = value;
            }
        }
    }

    #endregion

Which caches the bytes as you read them allowing other possibilities..

Now that may also be not so much of an improvement however let's finally combine the concepts...

C#
#region LinkedStream

    /// <summary>
    /// Represents multiple streams as a single stream.
    /// </summary>
    /// <remarks>
    /// Turning into a interesting little looping buffer when you just call 
    /// AsPerpetual, would be a cool idea for a RingBuffer
    /// </remarks>
    public class LinkedStream : System.IO.Stream, IEnumerable<System.IO.Stream>, 
    IEnumerator<System.IO.Stream>, IEnumerable<byte>, IEnumerator<byte>
    {
        #region Fields

        internal protected IEnumerable<System.IO.Stream> m_Streams;

        internal protected IEnumerator<System.IO.Stream> m_Enumerator;

        internal protected ulong m_TotalPosition = 0;

        #endregion

        #region Propeties

        public override bool CanRead
        {
            get { return ((IEnumerator<System.IO.Stream>)this).Current.CanRead; }
        }

        public override bool CanSeek
        {
            get { return ((IEnumerator<System.IO.Stream>)this).Current.CanSeek; }
        }

        public override bool CanWrite
        {
            get { return ((IEnumerator<System.IO.Stream>)this).Current.CanWrite; }
        }

        public override void Flush()
        {
            ((IEnumerator<System.IO.Stream>)this).Current.Flush();
        }

        public override long Length
        {
            get { return m_Streams.Sum(s => s.Length); }
        }

        public override long Position
        {
            get
            {
                return (long)(m_TotalPosition) + 
                ((IEnumerator<System.IO.Stream>)this).Current.Position;
            }
            set
            {
                Seek(value, System.IO.SeekOrigin.Current);
            }
        }

        public ulong TotalPosition { get { return m_TotalPosition; } 
        protected internal set { m_TotalPosition = value; } }

        #endregion

        #region Constructors

        public LinkedStream(IEnumerable<System.IO.Stream> streams) 
        {
            if (streams == null) streams = Enumerable.Empty<System.IO.Stream>();
            m_Streams = streams;
            m_Enumerator = streams.GetEnumerator();
        }

        public LinkedStream(System.IO.Stream stream) : this(stream.Yield()) { }

        
        public static LinkedStream LinkAll(params System.IO.Stream[] streams) 
        { return new LinkedStream(streams); }

        #endregion

        #region Methods

        public LinkedStream Link(params System.IO.Stream[] streams) 
        { return new LinkedStream(m_Streams.Concat(streams)); }

        public LinkedStream Unlink(int index, bool fromEnd = false)
        {
            if (index > m_Streams.Count()) 
            throw new ArgumentOutOfRangeException
            ("index cannot be greater than the amount of contained streams");
            IEnumerable<System.IO.Stream> streams = m_Streams;
            if (fromEnd) streams.Reverse();
            return new LinkedStream(m_Streams.Skip(index));
        }

        #endregion

        #region Wrappers

        public override int Read(byte[] buffer, int offset, int count)
        {
            return CoreRead(buffer, (ulong)offset, (ulong)count);
        }

        internal protected int CoreRead(byte[] buffer, ulong offset, ulong count, 
        System.IO.SeekOrigin readOrigin = System.IO.SeekOrigin.Current)
        {
            switch (readOrigin)
            {
                case System.IO.SeekOrigin.Begin:
                    {
                        m_Enumerator.Reset();
                        if (m_Enumerator.MoveNext())
                        {
                            m_TotalPosition = 0;
                            goto case System.IO.SeekOrigin.Current;
                        }
                        break;
                    }
                case System.IO.SeekOrigin.Current:
                    {
                        int read = 0;
                        while (count > 0)
                        {
                            read += ((IEnumerator<System.IO.Stream>)this).
                            Current.Read(buffer, (int)offset, (int)count);
                            count += (ulong)read;
                            m_TotalPosition += (ulong)read;
                            if (!m_Enumerator.MoveNext()) break;
                        }
                        return read;
                    }
                case System.IO.SeekOrigin.End:
                    {
                        //Reverse is unique (Could be combined though...)

                        //Take a snapshot in reverse
                        IEnumerable<System.IO.Stream> reverse = m_Streams.Reverse();

                        //Get that enumerator
                        IEnumerator<System.IO.Stream> enumerator = reverse.GetEnumerator();

                        //Take the length of the snapshot
                        m_TotalPosition = (ulong)reverse.Sum(s => s.Length);

                        //Determine how many from the end we are
                        int fromTheEnd = 0;

                        //As we go
                        while (enumerator.MoveNext() && offset > 0)
                        {
                            //Move offset
                            offset -= (ulong)m_Enumerator.Current.Length;
                            //If we have to move
                            m_TotalPosition -= (ulong)m_Enumerator.Current.Length;
                            //Keep track
                            ++fromTheEnd;
                        }

                        //Move the enumerator
                        m_Enumerator = m_Streams.Skip(m_Streams.Count() - 
					fromTheEnd).GetEnumerator();
                        break;
                    }
                default:
                    {
                        Utility.BreakIfAttached();
                        break;
                    }
            }
            return 0;
        }

        public override long Seek(long offset, System.IO.SeekOrigin origin) 
        { return (long)CoreSeek((ulong)offset, origin); }

        internal protected ulong CoreSeek(ulong offset, 
        System.IO.SeekOrigin origin, bool moveTotalPosition = false)
        {
            ulong total = 0;

            switch (origin)
            {
                case System.IO.SeekOrigin.Current:
                    {
                        IEnumerable<System.IO.Stream> streams = m_Streams;

                        IEnumerator<System.IO.Stream> enumerator = streams.GetEnumerator();

                        if (offset < 0) streams = streams.Reverse();

                        while (offset > 0)
                        {
                            total += (ulong)Math.Abs
			(enumerator.Current.Seek((long)offset, origin));
                            offset -= total;
                            enumerator.MoveNext();
                        }

                        if (moveTotalPosition)
                        {
                            m_TotalPosition = (ulong)total;
                            m_Enumerator = enumerator;
                        }

                        break;
                    }
                case System.IO.SeekOrigin.Begin:
                    {
                        //If we have to move
                        IEnumerator<System.IO.Stream> enumerator;
                        if (moveTotalPosition)
                        {
                            enumerator = ((IEnumerator<System.IO.Stream>)this);
                            enumerator.Reset();
                            enumerator.MoveNext();
                        }
                        else enumerator = m_Streams.GetEnumerator();

                        while (m_Enumerator.MoveNext() && offset > 0)
                        {
                            //Move the offset
                            offset -= (ulong)m_Enumerator.Current.Length;
                            //If we have to move
                            if (moveTotalPosition) m_TotalPosition += 
					(ulong)m_Enumerator.Current.Length;
                        }

                        break;
                    }
                case System.IO.SeekOrigin.End:
                    {

                        IEnumerable<System.IO.Stream> reverse = m_Streams.Reverse();

                        IEnumerator<System.IO.Stream> enumerator = reverse.GetEnumerator();

                        //If we have to move
                        if (moveTotalPosition) m_TotalPosition = (ulong)reverse.Sum(s => s.Length);

                        while (enumerator.MoveNext() && offset > 0)
                        {
                            //Move offset
                            offset -= (ulong)m_Enumerator.Current.Length;
                            //If we have to move
                            if (moveTotalPosition) m_TotalPosition -= 
					(ulong)m_Enumerator.Current.Length;
                        }

                        //If we have to move
                        if (moveTotalPosition) m_Enumerator = enumerator;

                        break;

                    }
                default:
                    {
                        Utility.BreakIfAttached();
                        break;
                    }
            }

            //Calculate
            total += (ulong)(offset > 0 ?
                m_Enumerator.Current.Seek((long)offset, System.IO.SeekOrigin.Begin) :
                    m_Enumerator.Current.Seek((long)offset, System.IO.SeekOrigin.End));

            return total;
        }

        public override void SetLength(long value)
        {
            if ((ulong)value < m_TotalPosition)
            {
                IEnumerator<System.IO.Stream> enumerator = m_Streams.Reverse().GetEnumerator();

                ulong offset = 0;

                IEnumerable<System.IO.Stream> streams = m_Streams.TakeWhile(s =>
                {
                    offset += (ulong)s.Length;
                    return (value -= s.Length) > 0;
                });

                m_Streams = streams;

                m_Enumerator = streams.GetEnumerator();

                if (offset > 0) offset += CoreSeek((ulong)offset, System.IO.SeekOrigin.Begin);
                else offset += CoreSeek((ulong)offset, System.IO.SeekOrigin.End);

                m_TotalPosition = (ulong)offset;
            }
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            if (count < CoreWrite(buffer, (ulong)offset, (ulong)count))
            {
                Utility.BreakIfAttached();
            }
        }

        ///Todo => Test and complete
        internal protected int CoreWrite(byte[] buffer, ulong offset, 
        ulong count, System.IO.SeekOrigin readOrigin = System.IO.SeekOrigin.Current)
        {
            switch (readOrigin)
            {
                case System.IO.SeekOrigin.Current:
                    {
                        return ((IEnumerator<System.IO.Stream>)this).
                        Current.Read(buffer, (int)offset, (int)count);
                    }
                case System.IO.SeekOrigin.Begin:
                    {
                        return m_Streams.First().Read(buffer, (int)offset, (int)count);
                    }
                case System.IO.SeekOrigin.End:
                    {
                        return m_Streams.Last().Read(buffer, (int)offset, (int)count);
                    }
                default:
                    {
                        Utility.BreakIfAttached();
                        break;
                    }
            }
            return 0;
        }

        IEnumerator<System.IO.Stream> IEnumerable<System.IO.Stream>.GetEnumerator()
        {
            return m_Enumerator;
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return m_Enumerator;
        }

        System.IO.Stream IEnumerator<System.IO.Stream>.Current
        {
            get { return m_Enumerator.Current; }
        }

        void IDisposable.Dispose()
        {
            m_Enumerator.Dispose();
            m_Streams = null;
            base.Dispose();
        }

        object System.Collections.IEnumerator.Current
        {
            get { return (object)((IEnumerator<System.IO.Stream>)this).Current; }
        }

        bool System.Collections.IEnumerator.MoveNext()
        {
            return m_Enumerator.MoveNext();
        }

        void System.Collections.IEnumerator.Reset()
        {
            m_Enumerator.Reset();
        }

        IEnumerator<byte> IEnumerable<byte>.GetEnumerator() 
        { return new EnumerableByteStream(this).GetEnumerator(); }

        byte IEnumerator<byte>.Current { get 
        { return ((IEnumerable<byte>)this).GetEnumerator().Current; } }

        #endregion
    }

    #endregion

You can see how this couples with the previous concept and how each piece of the puzzle gets one step bigger along the way however in the end you have a pleasant result which is not only easy to maintain but allows you to provide additional features without breaking changes in existing code.

Some of these classes should have been present in the Core Framework but I feel overall the reason they are not is because these patterns demonstrated are so using techniques which facade over the preferred Core Framework styles in some situations however I believe they are all still complaint and still just as if not more interesting.

All of this is just a preview of what is to come for Managed Media Aggregation, get ready for more drawn out boring articles which will outline interesting tactics developed along the way!

My next article will have some goodies on Socket Communication such as a Pool implementation for Async usage, Statistic keeping classes such as a Counter and their Encapsulated forms and much more.

New Rtcp features include a more structured Packet design which is then subsequently unified with Rtp (Rtsp and expandable to all other packets under the Interface IPacket).

I have broken down the TransportContext and added an additional two aspects to allow for Sending and Receiving to multiple people at the same time.

The TrasnportContext now derives from System.Net.TransportContext proper.

Information and Control over Bandwidth is now available.

The ChannelBindings implemented are RtpChannelBinding, RtspChannelBinding and HttpChannelBinding.

Participant support has been added with features such as Active Participant support.

Overall the RtpClient still functions as a “Client” style class with Send style methods, it just abstracts everything completely not only for efficiency but to comply with the required standards for Rtp and Rtcp.

Async support has even been added!

There is a ton of stuff, however this article is long enough, so I will take this time to close and thank you for reading!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Livin in a lonely world, caught the midnight train going anywhere... Only thing is it was a runaway train... and it ain't ever goin back...
мала ка на хари, Trahentes ex exsilium

Comments and Discussions

 
-- There are no messages in this forum --