【问题标题】:C# Generics - cannot convert 'byte[]' to 'T'C# 泛型 - 无法将 'byte[]' 转换为 'T'
【发布时间】:2020-02-28 04:01:56
【问题描述】:

在 C# 中使用泛型类和方法时,我需要您的帮助。当我在ProduceData中调用EnqueObject方法时,在EnqueueObject(block)那一行,出现错误:cannot convert 'byte[ ]' 到 'T'。如有任何建议,我将不胜感激。

(我已经简化了我的代码,因为我相信我的问题是非常基本的)。

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);
    }
}

更新

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);
                }
            }
        }
    }
}

我了解到您需要进一步说明该程序的工作原理。它应该用于压缩和解压缩。首先,它按块(字节数组)读取文件,然后压缩块,将它们放入我自己的类 CompressedData 的对象中,并将它们序列化为一个文件。在解压过程中,对象被反序列化,压缩后的字节数组被解压到一个新文件中。它使用生产者-消费者模式,因此有一个“生产者”将元素放入队列中,队列本身和一个“消费者”获取元素并处理所需的操作(压缩/解压缩)。我想使用我的类 CompressingProducer 作为操作、压缩和解压缩的“生产者”,这意味着它在压缩过程中将字节数组排入队列,在解压缩过程中将我的类 CompressedData 的对象排入队列。这就是我尝试使用泛型的原因。

【问题讨论】:

  • blockbyte[]EnqueueObject 只需要 T。您必须以某种方式将byte[] 转换为T,然后才能通过它。告诉我们更多相关信息?
  • 一般问题是您传递的值是特定类型的byte[],而对此没有任何保证,甚至没有任何理由相信泛型类型参数T 将是 byte[]。您的队列必须有T 类型的元素,但编译器无法证明Tbyte[]。事实上,如果它可以,那么通用方面将毫无意义,因为T 永远不可能是byte[] 以外的任何东西。如果 you 是肯定的 T 始终是 byte[],那么您可以按照标记的重复项进行投射。但它仍然留下为什么的问题
  • @PeterDuniho - 我重新提出了这个问题,因为我认为很明显这不是关于铸造,而是关于转换。重复的内容纯粹是关于铸造。
  • @PeterDuniho 谢谢你的回答。 T 不会总是 byte[],这就是为什么我决定使用带有泛型参数的方法。我相信 T 可以代表任何类型的对象 - ?
  • @AliceHekrdlová - 与初学者交流并不累人 - 如果初学者有礼貌并且愿意改进他们的问题(就像你一样),那将是一种乐趣。到目前为止,我唯一的评论是您不应该编辑问题以使现有的 cmets 和答案无效。所以我重新编辑了你的问题。我希望它仍然清楚。

标签: c# generics generic-programming generic-collections


【解决方案1】:

这个解决方案帮助了我:

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

【讨论】:

    【解决方案2】:

    在泛型类中不能将T指定为真正的类,只需将函数ProduceData带出类即可

    【讨论】:

      【解决方案3】:

      课内 T 是泛型类型,它不代表任何类型或可以接受任何类型值的类型,它代表由参数确定的特定类型。


      声明CompressingProducer的变量时需要传递类型参数,

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

      T = 字节[]。在这种情况下,T 与类型 byte[] 匹配,但我们讨论的是泛型类型,它可以是任何类型,而不仅仅是 byte []。

      考虑现在声明下面的 T 是字符串

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

      T = 字符串 但是CompressingProducer.EnqueueObject方法里面需要T参数,参数不等于byte[],等于string。

      泛型有助于处理任何类型,但类型取决于声明变量时作为参数传递的类型。 问题是将类型 T 视为 CompressingProducer 类中的任何类型。 T 是在声明任何 CompressingProducer 变量时由参数确定的类型。 因此,您不能将任何值传递给 EnqueueObject 方法,只能传递 T 类型的值。

      在创建变量时将类型作为参数传递时,始终将 T 视为虚拟/虚构/虚构类型,它是真实类型。


      如果您需要处理队列中的任何类型,请在类型参数中使用 object 类,在 C# 中,除了 object 之外的任何类都继承自 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);
              }
          }
      


      我的最终解决方案是修改ProduceData方法,添加另一个Func类型的参数,这个参数是一个委托,将byte[]转换为作为参数传递的类型。

      两个例子使用:
      T = 字符串
      T = 人(示例类型)

         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();
              }
          }
      

      【讨论】:

      • 你犯了和其他三个人一样的错误。在您真正了解问题所在之前,您已经发布了答案。例如,一旦您最终开始提出解决方案,您就引入了一个新对象(转换委托),却无法知道这是否可行。实际上,根据 OP 的代码,我希望在读取足够数量的“块”之前不会产生 T。单个块不太可能转换为 T 的实例,因此无法提供您建议的委托。
      • 我假设 Settings.blockSize 是生成一个 T 对象所需的大小。该文件包含多个准备入队的 Settings.blockSize 块。
      • “我假设 Settings.blockSize 是生成一个 T 对象所需的大小” -- 很清楚。但是你没有正当的理由做出这样的假设。它证明了您发布的代码是合理的,但原始问题中没有证据表明这是一个有效的假设。
      • 对于一段不完整的代码(不是原始的),我们必须做出近似。我们不能百分百确定。谁给出了适当的批准就是谁提出了这个问题。我不知道我的回答是否正确,但希望对您有所帮助。
      • “我不知道我的答案是否正确,但我希望它有所帮助” -- “我希望它有帮助”不是正确的衡量标准决定何时以及如何回答问题。相反,如果您不知道何时发布答案是否“正确”,那么更好的方法是根本不发布答案。否则会在 Stack Overflow 上充斥可能无关和无用的帖子,从而使查找有用信息变得更加困难。
      【解决方案4】:

      ProduceData()byte[] 紧耦合,所以Queue&lt;T&gt; 中的元素类型应该相同:

      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);
          }
      }
      

      更新

      为了保持通用性,您应该这样做:

      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];
              }
          }
      }
      

      【讨论】:

      • 从问题作者提到“通用”的次数可以清楚地看出,他们打算让他们的代码保持通用。然而,您提出的更改完全删除了通用方面。我看不出这个答案对原始问题有什么用处。
      • 你完全正确,我更新了我的答案。谢谢。
      • 我不确定新版本的代码是否真正解决了问题的精神。当然,它可以编译。但是现在唯一支持 enqueue byte[] 块的类型仍然是非泛型的。坦率地说,我认为现在提供答案还为时过早,假设应该提供一个答案,然后再了解是什么导致问题的作者认为他们首先可以在泛型类中对数组进行排队。一个好的答案不仅仅是编译;它将解决他们的问题。在我们了解实际问题之前,没有办法发布一个好的答案。
      • 我明白你的意思,最好等待作者的反馈,但是ProducerData()方法有一个使用byte[]的具体实现,任何其他不想使用byte[]的类必须创建通过继承抽象类 CompressingProducer 并实现 ProducerData() 方法。
      • "ProducerData() 方法有一个使用 byte[]" 的具体实现——当然,这实际上是正确的。但到目前为止,我们不知道为什么会这样,也不知道 OP 期望它如何工作。也许队列本身不应该是通用的。也许应该,但应该有一些其他方法或委托可以将byte[] 转换为T。也许 none 的代码应该是通用的(使您的第一个提案更可取)。有几十种不同的方法可以修改代码,以便它能够以合理的方式编译和工作。但每一种方式都不一样,我们不知道哪种方式是正确的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-01
      • 1970-01-01
      相关资源
      最近更新 更多