Микроконтролери и електроника
http://mcu-bg.com/mcu_site/

Предаване на параметър на метод по стойност в C#
http://mcu-bg.com/mcu_site/viewtopic.php?f=14&t=18050
Страница 1 от 1

Автор:  [ Пет Окт 15, 2021 6:07 pm ]
Заглавие:  Предаване на параметър на метод по стойност в C#

C# по подразбиране предава параметрите на методите по адрес (reference), изключения са
базовите типове данни (int, double, string и т.н.), както и прости структури (които нямат
масиви например). Имам следната C# структура:
Код:
public struct TPCANMsg
{
    public uint ID;
    [MarshalAs(UnmanagedType.U1)]
    public TPCANMessageType MSGTYPE; 
    public byte LEN;     
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public byte[] DATA;   
}

Тази структура ще се добавя от работна нишка в стандартна System.Collections.Generic.Queue,
след което сигнализира главната нишка за последваща обработка.
Направих нещо съвсем просто: заделих памет за TPCANMsg в работната нишка и всеки път когато
дойдат данни ги копирам в нейните полета, след което викам Queue.Enqueue(msg). Понеже DATA[] е масив,
C# го предава на Enqueue() като адрес и съответно в опашката всички предходни елементи
се променят, което не искам да бъде така.

Целият код е по-долу:
Код:
public void PrintPCANMsg(TPCANMsg msg)
{
    string szHex = Convert.ToString(msg.ID, 16);
    szHex = szHex.ToUpper();
    Console.WriteLine("ID: 0x{0}; LEN: {1}; DATA[]:" +
        " 0x{2:X}, 0x{3:X}, 0x{4:X}, 0x{5:X}, 0x{6:X}, 0x{7:X}, 0x{8:X}, 0x{9:X}",
        szHex, msg.LEN, msg.DATA[0], msg.DATA[1], msg.DATA[2], msg.DATA[3], msg.DATA[4], msg.DATA[5], msg.DATA[6], msg.DATA[7]);
}

public void QueueTest()
{
    Queue<TPCANMsg> queue = new Queue<TPCANMsg>();
    TPCANMsg msg = new TPCANMsg();
    msg.DATA = new byte[8];

    Console.WriteLine("Enqueue() method: ");

    msg.LEN = 8;
    msg.MSGTYPE = TPCANMessageType.PCAN_MESSAGE_STANDARD;
   
    /// Enqueue 1st msg
    ///
    msg.DATA[0] = 0x11; msg.DATA[1] = 0x12; msg.DATA[2] = 0x13; msg.DATA[3] = 0x14;
    msg.DATA[4] = 0x15; msg.DATA[5] = 0x16; msg.DATA[6] = 0x17; msg.DATA[7] = 0x18;
    msg.ID = 0x18FFD61E;

    queue.Enqueue(msg);
    PrintPCANMsg(msg);

    /// Enqueue 2nd msg
    ///
    msg.DATA[0] = 0x21; msg.DATA[1] = 0x22; msg.DATA[2] = 0x23; msg.DATA[3] = 0x24;
    msg.DATA[4] = 0x25; msg.DATA[5] = 0x26; msg.DATA[6] = 0x27; msg.DATA[7] = 0x28;
    msg.ID = 0x18FFE01E;
    queue.Enqueue(msg);
    PrintPCANMsg(msg);

    /// Dequeue and Print all messages
    ///
    Console.WriteLine("Dequeue() method: ");
    while ( queue.Count > 0 )
    {
        msg = queue.Dequeue();
        PrintPCANMsg(msg);
    }
}


Резултата от кода:
Код:
Enqueue() method:
ID: 0x18FFD61E; LEN: 8; DATA[]: 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18
ID: 0x18FFE01E; LEN: 8; DATA[]: 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28
Dequeue() method:
ID: 0x18FFD61E; LEN: 8; DATA[]: 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28
ID: 0x18FFE01E; LEN: 8; DATA[]: 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28


Както се вижда от резултата C# е подал DATA[] масива като адрес, затова
изваждайки двата елемента имам еднакви DATA[], докато ID е наред.

Най-тъпото което се сетих е да променя структурата така:
Код:
public struct MYCANMsg
{
    public uint ID;
    public TPCANMessageType MSGTYPE;
    public byte LEN;
    public byte D0;
    public byte D1;
    public byte D2;
    public byte D3;
    public byte D4;
    public byte D5;
    public byte D6;
    public byte D7;
}

След което при Enqueue() и Dequeue() с опашката всичко е наред, понеже този път
в структурата няма масив и C# я предава като параметър по стойност.

Не ми се ще обаче да имам такава междинна структура, искам да е оригиналната с масив
в нея. Има ли начин на C# да се укаже да предаде структура/клас като параметър на метод
по стойност ? Ако няма, как постъпвате когато трябва да предадете по стойност ?

Автор:  slav4o.com [ Пет Окт 15, 2021 7:43 pm ]
Заглавие:  Re: Предаване на параметър на метод по стойност в C#

Ами видимо имаш само един физически създаден масив msg.DATA = new byte[8]; при Dequeue() пак дърпаш "неговата инстанция", фактически нашиваш данните два пъти на едно място и при Dequeue() дърпаш последните данни.

....

Според мен трябва на второто зареждане на данни за тест, да напишеш още веднъж msg.DATA = new byte[8]; и да инициализираш новите данни. Така ще имаш 2 физически масива в паметта които да държат данните.

edit: Ако данните за масива идват от масив, може да използваш Array.Copy да не ги зареждаш с цикъл елемент по елемент. То този метод сигурно така работи де...

Автор:  palavrov [ Нед Окт 17, 2021 6:36 pm ]
Заглавие:  Re: Предаване на параметър на метод по стойност в C#

Трябва да си направиш shallow или deep clone на обекта - пусни едни гугле и ще намериш достатъчно страници как да подкараш всичко.

Автор:  [ Пон Окт 18, 2021 4:32 pm ]
Заглавие:  Re: Предаване на параметър на метод по стойност в C#

Благодаря за отговорите.

Всъщност бях разбрал, че му липсва
Код:
new byte [8]
,
но по скоро ме интересуваше някакъв друг начин. Ще видя тези
shallow и deep clone, но видях че C# предлага и още един начин, fixed:
Код:
public struct CANMsg
{
    public uint ID;
    public TPCANMessageType MSGTYPE;
    public byte LEN;
    public fixed byte DATA[8];
}


но при компилиране дава грешка:

Код:
Severity   Code   Description   Project   File   Line   Suppression State
Error   CS0214   Pointers and fixed size buffers may only be used in an unsafe context   QueueExample3   C:\MyFile\Projects\CSharp\QueueExample3\CANQueue.cs   18   Active


Добавям unsafe към дефиницията на структурата:
Код:
public unsafe struct CANMsg
{
    public uint ID;
    public TPCANMessageType MSGTYPE;
    public byte LEN;
    public fixed byte DATA[8];
}


и сега се оплаква, че:

Код:
Severity   Code   Description   Project   File   Line   Suppression State
Error   CS0227   Unsafe code may only appear if compiling with /unsafe   QueueExample3   C:\MyFile\Projects\CSharp\QueueExample3\CANQueue.cs   13   Active


Та сега въпроса ми е: fixed и unsafe за структурата, компилирам проекта като unsafe и работи под Windows,
но това unsafe/fixed дали няма да създаде подводни камъни при билдване на други платформи ?

C# е малко като манджа с грозде :D

Автор:  slav4o.com [ Пон Окт 18, 2021 8:51 pm ]
Заглавие:  Re: Предаване на параметър на метод по стойност в C#

Ами когато включиш unsafe опцията вече може и така да стане :)

Доколкото виждам struct във C# еквивалентно на typedef struct във C/C++. T.e. това не е обект а сложен тип. Т.е. трябва да напишеш нещо от сорта на myStruct CANMsg; naBaiIvanStructurata CANMsg;
Всъщност защо не използваш само класове ?

Принципно всеки метод получава аргументите по стойност. Референтните типове са пак стойност която държи тип и адрес на обекта към който сочат.

За да може реално Dequeue() да извлече данните обекта който се подава на Queue() трябва да съществува в паметта във момента на Dequeue() така той реално ще съхранява физически въпросният масив, а в опашката ще имаш референция към него. Ако обектът който се подава на Queue() е Dispose()-нат към момента на Dequeue() естествено трябва да си правиш локални копия в твоите обекти от опашката. :)

Автор:  [ Вто Окт 19, 2021 2:03 pm ]
Заглавие:  Re: Предаване на параметър на метод по стойност в C#

Не ползвам само класове, защото чета от C++ DLL, и там данните са struct.

Автор:  valioman [ Сря Окт 20, 2021 10:24 pm ]
Заглавие:  Re: Предаване на параметър на метод по стойност в C#

Добре си се сетил за unsafe .. по принцип щом ти ще си менаджираш паметта с подобни гимнастики трябва да го кажеш на билдера. Отиваш на Properties на проекта и цъкаш отметката AllowUnsafeCode от Build менюто

Прикачени файлове:
Untitled.jpg
Untitled.jpg [ 256.83 KiB | Прегледано 2139 пъти ]

Страница 1 от 1 Часовете са според зоната UTC + 2 часа [ DST ]
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
http://www.phpbb.com/