none
Указатели и генерики как быть? RRS feed

  • Общие обсуждения

  •   public unsafe class MyType<T> where T : struct
      {
        T* mVar;
      }
    
    Cannot take the address of, get the size of, or declare a pointer to a managed type ('T'). С каких пор структуры стали managed type
    • Перемещено Abolmasov Dmitry 22 апреля 2011 г. 11:22 (От:Настольные ПК)
    • Изменен тип PashaPashModerator 29 сентября 2012 г. 22:47
    1 апреля 2011 г. 16:48

Все ответы

  •  

    Указателем может быть:

    Any user-defined struct type that contains fields of unmanaged types only

    http://msdn.microsoft.com/en-us/library/y31yhkeb(VS.80).aspx

     

    Посмотрите эту ссылку, здесь решаеться схожая задача: 

    http://social.msdn.microsoft.com/forums/en-US/csharplanguage/thread/095d539d-a887-445d-92b3-ccc1f6d0a352/

    Так что к сожалению структура вобщем являеться управляемым типом.


    Don't forget to mark the correct answer Blog
    1 апреля 2011 г. 21:08
  • >> Any user-defined struct type that contains fields of unmanaged types only

    Ну у меня как раз такие структуры, которые содержат только ValueType поля. 

     

    Как по вашему тогда работает этот метод который по указателю записывает массив структур типа T ???

    System.IO.UnmanagedMemoryAccessor.WriteArray<T>(long position, T[] array, int offset, int count) where T: struct;

    На мой взгляд эта ситуация, явный пример лени разработчиков компилятора. Поскольку компилятор вполне может отличать две структуры:

     

      struct Some_Valid
      {
        int field0;
        long field1
      }
    
      struct Some_Invalid
      {
        int   field0;
        string field1;
      }

    И если структура типа T 'не правильная' тогда и возникало бы это исключение, а так компилятор режет и правильное и не правильное. Что мне теперь делать без генерика? Создавать вручную n классов для всех ValueType структур? И я теперь думаю как бы донести эту мысль до разработчиков компилятора.

     

     

    5 апреля 2011 г. 15:12
  • Как работает метод - вы можете посмотреть с помощью рефлектора, или например здесь - UnmanagedMemoryAccessor.cs source code in C# .NET

    Мысли до разработчиков можно донести на сайте Microsoft Connect.


    Для связи [mail]
    8 апреля 2011 г. 11:07
  • Да я знаю, что могу посмотреть с помощью рефлектора. И вероятно я уже посмотрел. Вопрос в том посмотрел ли кто то еще. А там используются не документированные возможности C# например __makeref и в итогде все упирается в некую внешнюю функцию extern StructureToPtrNative. Что приводит меня в изумление, значит себе они понаделали хаков, а что делать остальным?

    8 апреля 2011 г. 16:31
  • Generic-типы - это не шаблоны C++. В C# компилируется именно генерик тип, а конкретный тип в него подставляется уже на этапе выполнения. Например, можно рефлекшеном подгрузить твой MyType<T>, подставить туда любой (вообще любой) тип структуры, и создать экземпляр MyType<BadStructure>. Причем это можно сделать из стороннего кода, а не только из твоего.

    Сделано это ради того, чтобы генерики можно было использовать без исходного кода.

    Почитай, например, http://www.mycode.ws/index.php?elif=c/csharp2008011800361020080118.htm#EBH, или любую другую статью по генерикам времен C# 2.0

    Поэтому отсутствие "распознавания" структур - это не признак лени, а признак отсутствия телепатии и возможности видеть будущее. Лет 6 назад, при компиляции List<T>, компилятор тоже должен был выдать предупреждение о всех кривых типах которые в него подставят разработчики в течении 6 лет?


    My blog | My pet project
    9 апреля 2011 г. 9:18
    Модератор
  • Хочешь сказать компилятор не может определить можно ли подставлять данный тип или нельзя? Я сделал бы это за пару часов (максимум), это сделать легче чем определить все ли ветки условия возвращают значение... И в крайнем случае можно было бы ввести тип UnmanagedStruct или смотреть что бы unsafe struct состоял только из ValueType. Проблема существует и как я уже говорил, когда они сами столкнулись с ней они использовали сразу два хака своей же платформы.


    9 апреля 2011 г. 11:06
  • Этот код доказывает что компилятор уже умеет отличать managed и unmanaged структуры. 

    using System;
    
    namespace ConsoleApplication5
    {
      unsafe class Program
      {
        static void Main(string[] args)
        {
          var val0 = new Some_Invalid(123, "Test");
          var val1 = new Some_Valid();
          
          var ptr1 = &val1;
          var ptr0 = &val0;
        }
    
        struct Some_Valid
        {
          int field0;
          int field1;
        }
    
        struct Some_Invalid
        {
          public Some_Invalid(int field0, string field1)
          {
            this.field0 = field0;
            this.field1 = field1;
          }
    
          int  field0;
          string field1;
        }
      }
    }
    
    Что же тогда мешает делать тоже самое при использовании генериков?

     

    9 апреля 2011 г. 11:47
  • Я хочу сказать, что конкретный тип можно подставить и после компиляции. И подставляет тип туда среда исполнения, на основе метаданных генериков. Сделать можно (все можно сделать :), но это не 2-3 часа.

    Конкретный пример:

    1. Предположим, ты как-то собрал свою dll с генериком (научил компилятор проверять все пути).
    2. Вася Пупкин поставил ссылку на твою dll. Объявил переменную типа MyType<BadStructure>. Он должен получить ошибку компиляции? Какую именно?
    3. Тот же Вася подгрузил твою сборку динамически, достал typeof<MyType<>> и динамически же подставил туда BadStructure. Он должен получить ошибку? Какую? В какой момент?
    4. А теперь то же самое, но в ситуации когда Васе досталась новая версия твоей сборки. В старой можно было подставлять любую структуру. В новой - только структуру с указателем. В какой момент пользователь должнен получить ошибку?

    Явно не 2-3 часа. Скорее 2-3 недели. Ради фичи, которую попросил 1 человек.


    My blog | My pet project
    9 апреля 2011 г. 20:04
    Модератор
  • Вот вам три способа:

    I

     

    Если в генерике встречается T*  т.е. если тип используется в указателях, то у данного генерика появляется некий атрибут показывающий, что T требует доп. проверки. Проверять что бы членами структуры были только ValueType, fixed buffers, и прочие UnmanagedStruct

     

    II

    2. Да ту же самую, что и в примере выше: Cannot take the address of, get the size of, or declare a pointer to a managed type

    3. А как вася будет использовать эту динамическую библиотеку? Я хочу сказать что, он должен будет сначала компилировать свой проект вместе с этой библиотекой, которую в дальнейшем будет загружать динамически. Других способов вроде не существует. Если только какие то извращения с рефлексией. Поэтому здесь все тоже самое, что и в пункте 2.

    >> В какой момент?

    В момент компиляции. По сути ничего дополнительно делать не надо, уже все есть. Разрешить использовать указатели в генериках и пусть остальное останется на откуп разработчиков. Тот кто использует указатели и так действует на свой страх и риск. И я думаю всякие дилетанты не работают с указателями. Мне лично такое ограничение не принесло никакой пользы и вам я уверен тоже.

    4. Если так рассуждать можно далеко зайти. А что если в 5й версии int будет называть iNt ??? Новая версия это новый код, новый продукт и обратная совместимость только на совести разработчика.

    III

     

    Логичнее и проще всего создать тип UnsafeStruct или UnmanagedStruct и научить компилятор работать с ним.

     



    10 апреля 2011 г. 0:21
  • >> Ради фичи, которую попросил 1 человек.

    Когда я искал инфу по этому вопросу я видел, что точно такая же проблема была и у других людей.



    10 апреля 2011 г. 0:24
  • рефлекшн - не извращение, а гораздо более часто используемых механизм, чем указатели. 

    В чем собственно вопрос?

    Почему нет типа unmanaged struct? Потому что .NET - управляемая среда. Рефлекшн и управляемые типы для нее родные. А unmanaged и указатели - костыли для работы с native кодом. И их скорее всего развивать не будут, т.к. их и так достаточно для полноценной работы со всяким pinvoke.

    Почему в генерике нельзя использовать указатель? Потому что на генерике не стоит constraint "unsafe struct" на параметр T. - того самого аттрибута "T требует доп проверки". И поставить нельзя, потому что такого типа констрейнта нет.

    Почему нет автовывода констрейнтов? Потому что public interface класса в C# задавается явно.

    Почему нет такого типа констрейнта? Да потому что это очень редкий случай. Намного реже чем "есть конструктор с одним параметром типа X" или "у класса T есть оператор +". Которые, кстати, тоже не поддерживаются. 

    Я не утверждаю, что что-то "мешает это сделать". Напиши в коннект, уговори сотню-другую людей проголосовать - и наверняка сделают. Нет смысла уговаривать меня это сделать :) И тем более обвинять разработчиков компилятора в лени.


    My blog | My pet project
    10 апреля 2011 г. 17:08
    Модератор
  • >> рефлекшн - не извращение, а гораздо более часто используемых механизм, чем указатели. 

     

    Друг не все пишут программы типа хелло-ворд. Поэтому не надо сравнивать рефлекшен с указателями область применения разная. Я к тому говорю, что только дилетант может называть указатели кастылем. Вобщето указатели они как то ближе к железу и к стихии компа, нежели искусственно выведенные ссылки. И знаешь ведь не ссылки спасают от ошибок а светлая головушка. Некоторые алгоритмы на указателях работают на два порядка быстрее чем на ссылках. В моем случае АВЛ деревья, и знаешь мне платят за качественный код, можно сказать даже за лучший в мире код, я пишу только такой и получаю удовольствие от этого ну и не слабый гонорар. 


    10 апреля 2011 г. 21:29
  • Я не сравнивал указатели с рефлекшеном. Я всего лишь указал на частоту использования.

    Кодер, пишущий на C# лучший код (самый лучшй в мире код!) не представляет, как в C# работают генерики? И, судя по "искуственно выведенным ссылкам", не представляет как работают ссылки, зачем нужны, и чем они отличаются от указателей . У меня есть подозрение, это не светлая голова помогает писать код с указателями, а кое-что другое мешает писать код с нормальными классами ;) Скорее всего - повышенное самомнение и нахождение в квадратике "неосознанная некомпетентность".

    Как это вдруг один адрес в памяти (ссылка) начинает работать на 2 порядка (в 100 раз) медленее, чем второй адрес (указатель)? Интересно было бы посмотреть на пример такого магического "алгоритма.? Ссылки в C# по "железной" реализации, и по скорости работы - полный аналог указателей в C++. Только дилетант может этого не понимать.

    По теме: вы решили свою проблему? запостили баг в коннект? или так и будете продолжать ругаться на мизерную пролему, попутно пытаясь поразить меня, уже давно не кодера, своей крутизной? 


    My blog | My pet project
    10 апреля 2011 г. 21:50
    Модератор
  • Вы уже все приняли на свой счет и обиделись, зачем же задавать вопросы тому кто имеет завышенное самомнение и не отличает указатели от ссылок? 

    >> Интересно было бы посмотреть на пример такого магического "алгоритма.?

    Да наверное, интересно было бы посмотреть, но к сожалению я не могу это показывать. У меня АВЛ дерево работает поверх отображенных в памяти файлов, вобщем огромный такой индекс поиска. И как вы понимаете, что здесь производительность играет решающую роль. И будь я ваших убеждений я бы использовал стандартные методы класса MemoryMappedViewAccessor.Write() и О! это оказалось намного медленее чем если использовать указатели. Примерно во столько же, во сколько в этом примере, только в десять раз хуже:

     

    using System;
    using System.IO;
    using System.Collections.Generic;
    using System.Text;
    using System.Diagnostics;
    
    namespace ConsoleApplication6
    {
     unsafe class Program
     {
     static void Main(string[] args)
     {
     var buf = new byte[0x8000000];
     var count = buf.Length / 8;
    
     var mem = new MemoryStream(buf);
     var bw = new BinaryWriter(mem);
    
     var sw = Stopwatch.StartNew();
     
     // #1
     {
     for (long i = 0; i < count; ++i)
     {
     bw.Write(i);
     }
    
     Console.WriteLine("#1 - " + sw.ElapsedMilliseconds);
     }
    
     sw.Restart();
    
     // #2
     {
     fixed (byte* tmp = &buf[0])
     {
     var ptr = (long*)tmp;
    
     for (long i = 0; i < count; ++i)
     {
     ptr[i] = i;
     }
     }
    
     Console.WriteLine("#2 - " + sw.ElapsedMilliseconds);
     }
    
     Console.WriteLine("Complate");
     Console.ReadLine();
     }
     }
    }
    
    
    #1 - 883
    #2 - 65
    Complate

     

    Как видите указатели позволяют достичь более эффективного кода. Я кстати не то имел ввиду говоря про ссылки, вероятно ссылки такие же быстрые как и указатели, но зато объекты на которые они ссылаются, а вернее их методы проигрывают весьма существенно, из-за всей этой анти-указательной догмы на платформе .net Кроме того если у вас есть массив объектов и аналогичный массив структур. То каждый элемент массива объектов на x86 платформе будет весить на 4ре байта больше, а на платформе x64 на 8 байтов больше, чем элемент массива структур. А это серьезный оверхиад. И это еще не все, массив объектов на деле же является массивом ссылок на объекты, которые располагаются где то в куче, и куча это кстати структура данных дерево, добавление\удаление\получение элементов которой происходит за время O(Log2(N)). Чувствуете уже разницу между ссылками и указателями?

     

    >> запостили баг в коннект?

    Нет поскольку, не вижу смысла бадатся с бюрократией, и с такими же 'фанатиками ссылок' как вы.

     




    11 апреля 2011 г. 0:23
  • Хм. возможно, я по другому понимаю слово "алгоритм". Приведите один и тот же алгоритм, написанный на структурах и ссылках, и на объектах и ссылках. А не два разных способа заполнения массива, с форой в сторону указателей. Потому что ссылки и прочее подразумавают что тип объекта актуален, а не "массив байт". Простая замена byte [] на long [ ] и использование тупо цикла с buf[i] выигрывает по времени у указателей. 

    Я чувствую разницу в ссылках и указателях в том, что ссылки не плывут при сборе мусора. Это и есть причина "догмы".

    Есть разница по поведению между массивом структур и массивом объектов. И есть даже алгоритмы, срабатывающие для массивов объектов быстрее, чем для массивов структур (разворот, например). Но я не пытаюсь сделать из этого религию, и не называю других фанатиками.

    А в том топике я советовал использовать SQL Server, а не мутить с файлами отображенными в память. Но кто-то заявил "на самом деле я не считаю эту задачу какой нибудь мега сложной. Пара недель от силы." месяца так 3 назад. Это все тот же несложный алгоритм? Если да, то его стоимость приближается к SQL Server.

    >> Нет поскольку, не вижу смысла бадатся с бюрократией, и с такими же 'фанатиками ссылок' как вы.

    Тогда продолжайте есть кактус.


    My blog | My pet project
    11 апреля 2011 г. 5:29
    Модератор
  • >> А не два разных способа заполнения массива, с форой в сторону указателей

    Я же по моему дал обоснование, тому что я привел именно этот пример. Что касается слова алгоритм, то если мне память не изменяет, то алгоритм обозначает определенную последовательность действий. И пример этот не высосан из пальца, он прямо из жизни.

    >> Пара недель от силы." месяца так 3 назад.

    Ну саму БД сделали месяц назад. Результаты впечатляют, SQL сосет. Вот вам небольшая статистика

    Создание 1000000 новых записей в таблице:

    Запись весит 16 байт: 6200ms

    Запись весит 8 Кбайт: 6500ms

    Построение индекса 1000000 записей

    OneToOne : 240ms   фильтрация + сортировка

    OneToMany : 450ms группировка + фильтрация + сортировка

     




    11 апреля 2011 г. 13:41
  • Остается констатировать факт что в C# 5 проблема осталась. Это очень печально, а как хотелось ... Но решение есть, и еще какое страждущие люди нашли то, что снимает любые оковы программирования на языке C#! 

    PS: Если кто нить когда нить, найдет другое решение, маякните в этой теме плз.

    TO PashaPash Я вчера перед сном вспомнил еще один пример, как раз один из тех что вы просили. К примеру у вас есть массив int[]. Задача такая: необходимо инвертировать все старшие байты на младшие, у каждого элемента этого массива. Эта задача типична при создании всякого рода фильтров для изображений. Вобщем сделав это без указателей и с указателями вы поймете наконец что здесь рулит.
    • Изменено Max Charp 27 сентября 2012 г. 8:05
    26 сентября 2012 г. 21:30
  • вот это полезная вещь.
    с другой стороны... не каждый день оно надо,
    но может понадобиться наверняка

    27 сентября 2012 г. 7:49
  • Алгоритмы с активной работой с памятью проще писать на unmanaged-коде. Нужно выбирать правильный инструмент для решения задачи, а не жаловаться что микроскопом неудобно забивать гвозди.

    Если я буду инветрировать байты для большого массива - я или напишу это на C# с указателями, или напишу на C++ и прокину как extern в C#. И займет это у меня минут 15 в самом худшем случае. А если буду писать что-то вроде суровой глубокой рекурсии - возьму F# (ради .tail). И любой другой <не C#> для решения <задачи, которая не слишком хорошо решается на C#>.

    Но уж точно не буду ждать полтора года новой версии C# и придумывать на ночь примеры "где еще в C# не настолько гибок как C# если бы в нем была вот еще такая фишка, которая нужна лично мне потому что я не хочу использовать C++".

    27 сентября 2012 г. 16:34
    Модератор
  • Вы очень изворотливый :))) потому что спор вы проиграли, но не признаетесь.

    >> или напишу на C++

    Существуют причины по которым мы не можем использовать C++. Да и уж если в C# есть указатели, то наверняка они есть там не для красоты? а для того что бы с их помощью можно было решать задачи.

    >> Но уж точно не буду ждать полтора года новой версии C#

    И мы естественно не ждали полтора года новой версии C#, решение есть но оно не такое гибкое как хотелось бы. Мы создали класс под каждый используемый нами ValueType в итоге у нас 6 классов, вместо которых мог быть один, если бы в MS сделали нормальную поддержку генериков и указателей.


    • Изменено Max Charp 28 сентября 2012 г. 8:47
    27 сентября 2012 г. 17:45
  • Какой спор? Вы задали вопрос, я ответил, и попытался объяснить почему в C# нет указателей на генерики. Я прекрасно понимаю, как их можно было бы реализовать (например, введением ограничения на type parameter в стиле "pointerable"). И понимаю, что реализуют их нескоро (вообще никогда), потому что:

    • есть огромное количество более востребованных изменений в C#
    • есть C++ CLI, в котором под тот же .NET можно творить с указателями вообще все что угодно, и который выдает на выходе точно такие же .NET сборки.

    Для окончательного пользователя результат компиляции C++ CLI и C# не отличается вообще никак. Точно такие же классы в точно таких же сборках.

    >> Да и уж если в C# есть указатели, то наверняка они есть там не для красоты? а для того что бы с их помощью можно было решать задачи.

    Если в семействе .NET языков есть C++ с более гибкой работой с указателями, то наверняка он есть там не для красоты? а для того что бы с их помощью можно было решать задачи.

    Я не могу представить себе ситуацию, когда вы не можете (по каким-то неизвестным причинам) использовать C++ CLI, но при этом можете использовать совершенно стороннюю утилиту для инлайна IL.

    Т.е. на IL вам можно писать, а на C++, который в точно такой же IL скомпилируется - уже нельзя? И в этом виноват Microsoft - не предвидели они такой очевидной ситуации. Ок...


    28 сентября 2012 г. 11:00
    Модератор
  • PashaPash

    Я не против использования C++, просто не вижу смысла ради одного генерика создавать целый проект. Что касается инлайна IL то это интересно само по себе, даже в C++ нельзя делать инлайн IL, ASM можно но он платформозависимый. Кроме того иметь низкоуровневые возможности иногда действительно полезно в некоторых ситуациях. Другое дело что для 99% .Net программистов такие ситуации неведомы, потому что суть их работы это дергать методы из библиотек на все случаи жизни...

    ПС: Спасибо за дискуссию ваше мнение мне было очень интересным.




    • Изменено Max Charp 28 сентября 2012 г. 19:09
    28 сентября 2012 г. 19:07