1

I need your help with using generic class and methods in C#. When I call EnqueObject method in ProduceData, on the line with EnqueueObject(block), an error occurs: cannot convert 'byte[]' to 'T'. I would appreciate any advice.

(I've simplified my code because I believe my problem is something really basic).

class CompressingProducer<T>
{
    Queue<T> _queue;

    public void ProduceData(object fileInputStream)
    {
        byte[] block = new byte[Settings.blockSize];
        int bytesRead;

        while ((bytesRead = ((Stream)fileInputStream).Read(block, 0, Settings.blockSize)) > 0)     
        {
            EnqueueObject(block);   
            block = new byte[Settings.blockSize];
        }
    }

    private void EnqueueObject(T data) 
    {
        _queue.Enqueue(data);
    }
}

UPDATE

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.IO;

namespace GZipTestProject
{
    class CompressingProducer<T>
    {           
        Thread _producerThread;
        readonly object _lock;
        Queue<T> _queue;

        /// <summary>
        /// Reads data in chunks (as byte[]) or as CompressedData objects from the file stream and inserts them into a queue
        /// </summary>
        /// <param name="fileInputStream">Connected to the file from which data will be read</param>
        public CompressingProducer(Stream fileInputStream) {
            _lock = ProducerConsumer<T>.getLock();
            _queue = ProducerConsumer<T>.getQueue();

            _producerThread = new Thread(ProduceData);
            _producerThread.Start(fileInputStream);
        }

        /// Takes a file input stream parameter from which data will be read and put into a queue for a consumer.
        /// The parameter of this method must be object because it will be passed as a delegate to a new thread.
        public void ProduceData(object fileInputStream)
        {
            if (GZipTest.GetActionType() == ActionType.Compress) {  

                byte[] block = new byte[Settings.blockSize];
                int bytesRead;

                while ((bytesRead = ((Stream)fileInputStream).Read(block, 0, Settings.blockSize)) > 0)     
                {                    
                    if (bytesRead < block.Length)
                    {
                        byte[] block2 = new byte[bytesRead];
                        Array.Copy(block, block2, bytesRead);
                        block = block2;
                    }

                    EnqueueObject(block);   // put the data block into the queue
                    block = new byte[Settings.blockSize];
                }
        }

        private void EnqueueObject(T data) //byte[] block  or CompressedData   
        {
            lock (_lock)
            {
                while (_queue.Count >= Settings.maxQueueSize)
                {
                    Monitor.Wait(_lock);     // suspends the whole main thread of the application
                }

                _queue.Enqueue(data);

                if (_queue.Count == 1)
                {
                    // wake up any blocked dequeue, i.e. the consumer thread
                    Monitor.PulseAll(_lock);
                }
            }
        }
    }
}

I've understood you need a further explanation of how the program should work. It should be used for compression and decompression. At first, it reads a file by blocks (byte arrays), then compresses the blocks, puts them in objects of my own class CompressedData and serializes them into one file. During the decompression the objects are deserialized and the zipped byte arrays are decompressed into a new file. It uses producer-consumer pattern, so there is a "producer" putting elements in a queue, the queue itself and a "consumer" taking elemens and processes the required operation (compression/decompression). I want to use my class CompressingProducer as "producer" of both operations, compression and decompression, it means it enqueues byte arrays during compression, and objects of my class CompressedData during decompression. It's why I'm trying to use generics.

TheVillageIdiot
  • 40,053
  • 20
  • 133
  • 188
  • `block` is a `byte[]`. `EnqueueObject` only takes a `T`. You must somehow convert the `byte[]` to `T` before you can pass it. Tell us more about that? – Enigmativity Feb 28 '20 at 04:21
  • 1
    The general problem is that the value you are passing is of a specific type `byte[]`, while there's not any guarantee, or even any reason at all to believe for that matter, that the generic type parameter `T` is going to be `byte[]`. Your queue must have elements of type `T`, but the compiler can't prove that `T` is `byte[]`. Indeed, if it _could_, then the generic aspect would be pointless, because `T` would never be able to be anything other than `byte[]`. If _you_ are positive `T` is always `byte[]`, then you can cast per the marked duplicates. But it still leaves the question of **why**? – Peter Duniho Feb 28 '20 at 04:21
  • @PeterDuniho - I reopened the question because I thought it was clear that this isn't about casting and, instead, about converting. The duplicates were purely about casting. – Enigmativity Feb 28 '20 at 04:24
  • @PeterDuniho Thank you for your answer. T won't be always byte[], that's why I've decided to use method with a generic type of parameter. I've believed T could represent any type of object - ? – Alice Hekrdlová Feb 28 '20 at 04:40
  • @AliceHekrdlová - `T` is a placeholder for a type that you supply at run-time to create an instance and once you create that instance the type is locked in stone. For example, if you wrote `var x = new CompressingProducer` then `T` is forever `string` in the instance `x`. You would need to tell the code how to convert from a `byte[]` to a `string`. That's what's missing in your code now. – Enigmativity Feb 28 '20 at 04:45
  • @Alice: _"I've believed T could represent any type of object "_ -- yes, it can. That's why you are _not_ allowed to pass a value _known_ to be `byte[]`, to a method that requires a value of `T`. The method is expecting a `T`, whatever that happens to be when the class containing the method is instantiated. So, e.g. if you create a `CompressingProducer`, then the code will try to pass `byte[]` to a method that requires an `int`. That's illegal, and it's why the compiler is giving you the error. – Peter Duniho Feb 28 '20 at 05:03
  • @PeterDuniho Enigmativity Thank you both for trying to help and for being patient with my lack of knowledges. I realize it could be exhausting to communicate with beginners like me. But I really appreciate your time and effort. – Alice Hekrdlová Feb 28 '20 at 05:25
  • 1
    @AliceHekrdlová - It's not exhausting communicating with beginners - if the beginners are polite and willing to improve their question (like you are) then it's a pleasure. My only comment so far is that you shouldn't edit questions to invalidate existing comments and answers. So I've re-edited your question. I hope it is still clear. – Enigmativity Feb 28 '20 at 08:04

4 Answers4

3

this solution helped me:

return (T)Convert.ChangeType(MyVariable, typeof(T)); 
-1

You can't specify T as a real class in the generic class, just bring function ProduceData out of class

player2135
  • 111
  • 4
-1

Inside of class T is a generic type, it not represents any type or a type which can be accept any type of value, it represents a especific type determined by parameter.


When you declare a variable of CompressingProducer you need to pass the type parameter,

CompressingProducer<byte[]> producer = new CompressingProducer<byte[]>();

T = byte[]. In this case T matches with the type byte[] but we are talking about generic types which can be any type not only byte [].

Consider declaring following T now is string

CompressingProducer<string> producer = new CompressingProducer<string>();

T = string but inside the CompressingProducer.EnqueueObject method needs T parameter, parameter is not equals to byte[], is equal to string.

Generics helps to handle any type, but type depends on type passed as parameter when declare variable. The problem is to treat type T as any type inside the CompressingProducer class. T is a type determined by parameter when declaring any CompressingProducer variable. For this reason, you cannot pass any value to the EnqueueObject method, only values of type T.

Consider allways T as a virtual/fiction/imaginary type which is a real type when pass type as parameter when create a variable.


If you need to handle any type in the Queue use the object class in type parameter, in C# any class excepts object inherits from object.

class CompressingProducer
    {

        Queue<Object> _queue;

        public void ProduceData(object fileInputStream)
        {
            byte[] block = new byte[Settings.blockSize];
            int bytesRead;

            while ((bytesRead = ((Stream)fileInputStream).Read(block, 0, Settings.blockSize)) > 0)
            {
                EnqueueObject(block);//Needs object of T type.
                block = new byte[Settings.blockSize];
            }
        }

        private void EnqueueObject(Object data)
        {
            _queue.Enqueue(data);
        }
    }


My final solution is modify ProduceData method, add another parameter of type Func, this parameter is a delegate to convert from byte[] to type passed as parameter.

Two examples using:
T = string
T = Person (Example type)

   class CompressingProducer<T>
    {

        Queue<T> _queue;

        public void ProduceData(Stream fileInputStream, Func<byte[], T> convert)
        {
            byte[] block = new byte[Settings.blockSize];
            int bytesRead;

            while ((bytesRead = ((Stream)fileInputStream).Read(block, 0, Settings.blockSize)) > 0)
            {
                EnqueueObject(convert(block));//Using convert function, from byte[] ==> to T 
                block = new byte[Settings.blockSize];
            }
        }

        private void EnqueueObject(T data)
        {
            _queue.Enqueue(data);
        }
    }


    class Program
    {
        public static string ConvertData(byte[] data)
        {
            return Encoding.UTF8.GetString(data);
        }

        class Person
        {
            public long Id { get; set; }
            public String Name { get; set; }

            public byte[] ToBytes()
            {
                //this is aproach example, Needs more improvements
                return BitConverter.GetBytes(Id).Concat(Encoding.UTF8.GetBytes(Name).Take(Settings.blockSize - 8/* long type size of Id */)).ToArray(); //byte[] of blockSize size
            }

            public static Person ToPerson(byte[] bytes)
            {
                return new Person
                {
                    Id = BitConverter.ToInt64(bytes.Take(8).ToArray()),
                    Name = Encoding.UTF8.GetString(bytes.Skip(8).ToArray())
                };
            }
        }
        static void Main(string[] args)
        {
            //EXAMPLE 1
            CompressingProducer<string> producer = new CompressingProducer<string>();
            using (Stream filestream = File.OpenRead("path to file"))
            {
                //ProduceData(Stream fileInputStream, Func<byte[], string> convert), T now is string type.
                producer.ProduceData(filestream, data => Encoding.UTF8.GetString(data));//Using lambda expression. 
                producer.ProduceData(filestream, ConvertData);//Using delegate. //Same result as lambda.
            }

            //EXAMPLE 2
            CompressingProducer<Person> personProducer = new CompressingProducer<Person>();
            using (Stream filestream = File.OpenRead("path to file containing Person Blocks"))
            {
                //ProduceData(Stream fileInputStream, Func<byte[], Person> convert), T now is Person type.
                personProducer.ProduceData(filestream, data => Person.ToPerson(data));//Using lambda expression. 
            }

            Console.ReadLine();
        }
    }
Joma
  • 3,520
  • 1
  • 29
  • 32
  • You have made the same exact error that three other people have so far. You have posted an answer before you really understand what the question is. For example, once you finally get around to proposing a solution, you introduce a new object (a conversion delegate) without having any way to know whether that's even possible. Indeed, based on the OP's code, I would expect that no `T` could be produced until a sufficient number of "blocks" have been read. An individual block is unlikely to be convertible to an instance of `T`, so no delegate such as you proposed could be provided. – Peter Duniho Feb 28 '20 at 05:50
  • I asume that Settings.blockSize is the size needed to generate one T object. The file contains multiple blocks of Settings.blockSize ready to Enqueue. – Joma Feb 28 '20 at 05:57
  • _"I asume that Settings.blockSize is the size needed to generate one T object"_ -- clearly. But you have no valid reason to make that assumption. It justifies the code you posted, but there's no evidence from the original question that it's a valid assumption. – Peter Duniho Feb 28 '20 at 06:14
  • With an incomplete piece of code(not original) we have to make approximations. we can't be 100% sure. Who gives the appropriate approval is who asked the question. I don't know if my answer is correct or not, but I hope it helps. – Joma Feb 28 '20 at 06:46
  • _"I don't know if my answer is correct or not, but I hope it helps"_ -- "I hope it helps" is not the right metric to use to decide when and how to answer a question. To the contrary, if you don't know when you post the answer whether it "is correct or not", then the better approach is to not post the answer at all. To do otherwise is to clutter up Stack Overflow with _likely_ extraneous and useless posts, making it that much harder to find useful information. – Peter Duniho Feb 28 '20 at 07:03
-2

ProduceData() is tightly coupled with byte[], so the type of elements in the Queue<T> should have the same:

class CompressingProducer
{
    private readonly Queue<byte[]> _queue;

    public void ProduceData(object fileInputStream)
    {
        byte[] block = new byte[Settings.blockSize];

        while (((Stream)fileInputStream).Read(block, 0, Settings.blockSize) > 0)
        {
            EnqueueObject(block);
            block = new byte[Settings.blockSize];
        }
    }

    private void EnqueueObject(byte[] block)
    {
        _queue.Enqueue(block);
    }
}

Update

To keet the generic you should do something like this:

public abstract class CompressingProducer<T>
{
    public Queue<T> Queue { get; set; }

    public abstract void ProduceData(object fileInputStream);

    protected void EnqueueObject(T element)
    {
        Queue.Enqueue(element);
    }
}

public class CompressingProducerByByteArray : CompressingProducer<byte[]>
{
    public override void ProduceData(object fileInputStream)
    {
        byte[] block = new byte[Settings.blockSize];

        while (((Stream)fileInputStream).Read(block, 0, Settings.blockSize) > 0)
        {
            EnqueueObject(block);
            block = new byte[Settings.blockSize];
        }
    }
}
  • It is clear from the number of times the question's author has mentioned "generic", that they intend for their code to remain **generic**. And yet, your proposed change completely removes the generic aspect. I fail to see how this answer is in any way useful to the original question. – Peter Duniho Feb 28 '20 at 05:05
  • You are completely right, I updated my answer. Thanks. – Rafael Pimenta Feb 28 '20 at 05:17
  • 1
    I'm not sure that the new version of code really addresses the _spirit_ of the question. Sure, it can compile. But now the only type that will support enqueue `byte[]` blocks is still the non-generic one. Frankly, I think it's premature to offer an answer, assuming one should be offered at all, before understanding what it was that led the question's author to think they could queue arrays in the generic class in the first place. A good answer will do more than just compile; it will _solve_ their problem. Until we understand the actual problem, there's no way to post a good answer. – Peter Duniho Feb 28 '20 at 05:24
  • I understand your point, Will be better wait author's feedback, however the ProducerData() method has a concrete implementation using byte[], any other class that does not want to use byte[] must be created by inheriting from the abstract class CompressingProducer and implementing the ProducerData() method. – Rafael Pimenta Feb 28 '20 at 05:34
  • _"the ProducerData() method has a concrete implementation using byte[]"_ -- sure, that's factually correct. But so far, we don't know why it's like that, nor how the OP expected that to work. Maybe the queue itself shouldn't be generic. Maybe it should but there should be some other method or delegate that can convert `byte[]` to `T`. Maybe _none_ of the code should be generic (making your first proposal preferable). There are dozens of different ways to modify the code so that it would compile and work in a sensible way. But each of those ways is different, and we can't know which is right. – Peter Duniho Feb 28 '20 at 05:40
  • @PeterDuniho I've updated an explanation of my code, hope that's more clear now. – Alice Hekrdlová Feb 28 '20 at 07:22