tags:

views:

105

answers:

2

Hi guys,

Perhaps this question was already asked million times but I couldn't find similar topic. Is it possible to write a generic class with multiple 'where'-s that will be checked in compile time? Here is what I have today

public class MsBitsEnumWrapper<TC, TE> : IEnumerable<TC>
{
    internal class Helper : IEnumerator<TC>
    {
        private readonly TC[] _data;
        private int _pos = -1;
        private readonly IEnumBackgroundCopyJobs _jobs;
        private readonly IEnumBackgroundCopyFiles _files;

        // I miss C++ templates that allows me to implements this method in completelly generic fashion...
        public Helper(TE source)
        {
            _jobs = source as IEnumBackgroundCopyJobs;
            _files = source as IEnumBackgroundCopyFiles;

            uint count = 0;
            if (null != _jobs)
            {
                _jobs.GetCount(out count);
                _jobs.Reset();
            }
            else
                if (null != _files)
                {
                    _files.GetCount(out count);
                    _files.Reset();
                }

            _data = new TC[count];

            for (uint i = 0; i < count; ++i)
            {
                uint fetched = 0;
                if (null != _jobs)
                {
                    IBackgroundCopyJob job;
                    _jobs.Next(1, out job, ref fetched);
                    _data[i] = (TC)job;
                }
                else
                    if (null != _files)
                    {
                        IBackgroundCopyFile file;
                        _files.Next(1, out file, ref fetched);
                        _data[i] = (TC)file;
                    }
            }
        }

#region Implementation of IDisposable
        public void Dispose() { }
#endregion

#region Implementation of IEnumerator
        public bool MoveNext()
        {
            if (_pos < (_data.Length - 1))
            {
                _pos++;
                return true;
            }

            return false;
        }

        public void Reset()
        {
            _pos = -1;
        }

        public TC Current
        {
            get { return _data[_pos]; }
        }

        object IEnumerator.Current
        {
            get { return Current; }
        }
#endregion
    }

    private readonly Helper _enumerator;

    public MsBitsEnumWrapper(TE source) { _enumerator = new Helper(source); }

#region Implementation of IEnumerable
    public IEnumerator<TC> GetEnumerator() { return _enumerator; }
    IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
#endregion
}

Obviously I don't like it because I have to have switch in run-time that detect the type of generic argument. Is it possible to have something like this ?

    public class MsBitsEnumWrapper<TC, TE> : IEnumerable<TC>
        where TE : IEnumBackgroundCopyJobs, IEnumBackgroundCopyFiles
        where TC : IBackgroundCopyJob, IBackgroundCopyFile
    {
        internal class Helper : IEnumerator<TC>
        {
            private readonly TC[] _data;
            private int _pos = -1;
            private readonly TE _iface;

            // I miss C++ templates that allows me to implements this method in completelly generic fashion...
            public Helper(TE source)
            {
                _iface = source;

                uint count;
                _iface.GetCount(out count);
                _iface.Reset();

                _data = new TC[count];

                for (uint i = 0; i < count; ++i)
                {
                    uint fetched = 0;
                    TC job;
                    _iface.Next(1, out job, ref fetched);
                    _data[i] = job;
                }
            }

#region Implementation of IDisposable
            public void Dispose() { }
#endregion

#region Implementation of IEnumerator
            public bool MoveNext()
            {
                if (_pos < (_data.Length - 1))
                {
                    _pos++;
                    return true;
                }

                return false;
            }

            public void Reset()
            {
                _pos = -1;
            }

            public TC Current
            {
                get { return _data[_pos]; }
            }

            object IEnumerator.Current
            {
                get { return Current; }
            }
#endregion
        }

        private readonly Helper _enumerator;

        public MsBitsEnumWrapper(TE source) { _enumerator = new Helper(source); }

#region Implementation of IEnumerable
        public IEnumerator<TC> GetEnumerator() { return _enumerator; }
        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
#endregion
    }

I realize that it won't work if generic is defined with TE=IEnumBackgroundCopyJobs and TC=IBackgroundCopyFile, but since I'm the one who defines TE and TC and function prototypes of given interfaces are the same it would be nice to have it work this way.

Or may be there is another way to simplify current code ?

Thanks!

+1  A: 

It sounds like what you're asking is if C# supports structural typing in generics in the same way that C++ does for templates. The answer is No.

C++ templates aren't fully evaluated for correctness until the member functions are used with a given generic instantiation. C# generics on the other hand are fully evaluated on their own when they are compiled. It leaves no room for structural / match by name typing.

There are a couple of approaches you could take here. The more laborous approach is to create another interface for the GetCount and Reset functions. Then create wrapper types for the two interfaces to plug into these methods. This is a bit tedious though after a while.

My personal preference is to solve this problem with delegates.

public Helper(TE source, Func<TE,count> getCount, Action<TE> reset)
{
    _iface = source;

    uint count = getCount(_iface);
    reset(_iface);
    ...
}
JaredPar
A: 

As JaredPar indicated, you can't do this in C#. The choices are to use delegates, or go old school with using an abstract base class.

My implementation below take advantage of 'yield return' to cut down on the implementation of IEnumerable.

#if USE_DELEGATES
public class MsBitsEnum<T> : IEnumerable<T>
{
    Action _reset;
    Func<T> _next;
    Func<int> _count;

    public MsBitsEnum(Action reset, Func<T> next, Func<int> count)
    {
        _reset = reset;
        _next = next;
        _count = count;
    }

    public IEnumerator<T> GetEnumerator()
    {
        _reset();
        int count = _count();
        for (int i = 0; i < count; ++i)
            yield return _next();
    }

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

public class MsBitsJobs : MsBitsEnum<IBackgroundCopyJob>
{
    public MsBitsJobs(IEnumBackgroundCopyJobs jobs)
        : base(() => jobs.Reset(),
               () => 
               {
                   IBackgroundCopyJob job = null;
                   uint fetched = 0;
                   jobs.Next(1, out job, out fetched);
                   return fetched == 1 ? job : null;
               },
               () => 
               {
                   uint count;
                   jobs.GetCount(out count);
                   return (int) count;
               })
    {
    }
}

public class MsBitsFiles : MsBitsEnum<IBackgroundCopyFile>
{
    public MsBitsFiles(IEnumBackgroundCopyFiles files)
        : base(() => files.Reset(),
               () =>
               {
                   IBackgroundCopyFile file = null;
                   uint fetched = 0;
                   files.Next(1, out file, out fetched);
                   return fetched == 1 ? file : null;
               },
               () =>
               {
                   uint count;
                   files.GetCount(out count);
                   return (int)count;
               })
    {
    }
}

#else   // !USE_DELEGATES

public abstract class MsBitsEnum<T> : IEnumerable<T>
{
    protected abstract void Reset();
    protected abstract bool Next(out T item);

    public IEnumerator<T> GetEnumerator()
    {
        T item;
        Reset();
        while (Next(out item))
            yield return item;
    }

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

public class MsBitsJobs : MsBitsEnum<IBackgroundCopyJob>
{
    IEnumBackgroundCopyJobs _jobs;

    protected override void Reset()
    {
        _jobs.Reset();
    }

    protected override bool Next(out IBackgroundCopyJob job)
    {
        uint fetched;
        _jobs.Next(1, out job, out fetched);
        return fetched == 1;
    }

    public MsBitsJobs(IEnumBackgroundCopyJobs jobs)
    {
        _jobs = jobs;
    }
}

public class MsBitsFiles : MsBitsEnum<IBackgroundCopyFile>
{
    IEnumBackgroundCopyFiles _files;

    protected override void Reset()
    {
        _files.Reset();
    }

    protected override bool Next(out IBackgroundCopyFile file)
    {
        uint fetched;
        _files.Next(1, out file, out fetched);
        return fetched == 1;
    }

    public MsBitsFiles(IEnumBackgroundCopyFiles files)
    {
        _files = files;
    }
}
#endif  //  !USE_DELEGATES
Ants
I'll go with delegate version. Thanks a lot!
ruslan