گفتگو هایی در باب سی شارپ

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
خیلی ممنون استاد .
تم گرافیکی تلریک و کلا کمپوننت های تلریک را بکار بردم ولی توی فرم اصلی بکار نبردم . فرم اصلی رو هم که فقط باز میکنم (بدون باز کردن فرم تنظیمات) حدود 35 مگ رم رو اشغال میکنه . توی اون فرم ، فقط چند کنترل ساده و کوچیک transparentcontrol هه .

استاد ، کد زیر رو که اجرا میکنم :

کد:
    public static class ExtentionMethod
    {
        /// <summary>
        /// توسط نام درایو ، GUID مربوط به اون درایو را برمیگردونه .
        /// </summary>
        /// <param name="lpszVolumeMountPoint">
        /// نام درایو ای که GUID آنرا میخوایم . این نام ، باید با بک اسلش خاتمه پیدا کنه .
        /// </param>
        /// <param name="lpszVolumeName">
        /// مقدار GUID مربوط به درایوی را که در پارامتر lpszVolumeMountPoint مشخص کرده بودیم را بصورت خروجی به ما برمیگردونه .
        /// دقت کنید که این پارامتر ، خروجی هست .
        /// </param>
        /// <param name="cchBufferLength">
        /// طول بافر خروجی ، یعنی طول پارامتر رشته ی lpszVolumeName بر حسب TCHAR .
        /// اگر انکودینگ رشته ، بصورت ansi برای پارامتر lpszVolumeMountPoint در نظر گرفته بشه ، اندازه ی TCHAR برای هر کاراکتر ، یک بایت میشه وگرنه اگه unicode باشه ، اندازه ی هر کاراکتر براش 2 بایت در نظر گرفته میشه .
        /// اما با وجود طولانی ترین کاراکتر ، حداکثر 50 کاراکتر برای این پارامتر (یعنی 50 کاراکتر برای پارامتر دوم) کافی هست .
        /// </param>
        /// <returns>
        /// اگر متد ، موفقیت آمیز اجرا بشه ، مقدار بازگشتی ، غیر از صفر (یعنی true) وگرنه مقدار صفر (یعنی false) برگردونده میشه.
        /// </returns>
        [DllImport("Kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", CharSet = CharSet.Auto)]
        private static extern bool GetVolumeNameForVolumeMountPointW(string lpszVolumeMountPoint, out StringBuilder lpszVolumeName, int cchBufferLength);




        public static string GetVolumeGUID(this DriveInfo driveInfo)
        {
            StringBuilder lpszVolumeName = new StringBuilder(50);
            bool isSuccessCalling = ExtentionMethod.GetVolumeNameForVolumeMountPointW(driveInfo.Name, out lpszVolumeName, 50);
            MessageBox.Show(isSuccessCalling.ToString(), "GetVolumeGUID");
            return lpszVolumeName.ToString();
        }

    }

متد GetVolumeNameForVolumeMountPointW (که درون متد GetVolumeGUID هست) وقتی میخواد اجرا بشه ، ارور زیر رو میده :

کد:
System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'

ارور را میبره روی خط Application.Run (در متد Main) در کلاس Program .
واسه ی چی هه و چجوری باید رفع اش کرد؟
بجای StringBuilder در پارامتر دوم GetVolumeNameForVolumeMountPointW ، از string هم استفاده کردم اما همین ارور رو میده . باید از کلاس Marshal براش حافظه بگیرم تا جواب بده؟
ref هم کردم ، جواب نداد .
 

the_king

مدیرکل انجمن
خیلی ممنون استاد .
تم گرافیکی تلریک و کلا کمپوننت های تلریک را بکار بردم ولی توی فرم اصلی بکار نبردم . فرم اصلی رو هم که فقط باز میکنم (بدون باز کردن فرم تنظیمات) حدود 35 مگ رم رو اشغال میکنه . توی اون فرم ، فقط چند کنترل ساده و کوچیک transparentcontrol هه .

استاد ، کد زیر رو که اجرا میکنم :

کد:
    public static class ExtentionMethod
    {
        /// <summary>
        /// توسط نام درایو ، GUID مربوط به اون درایو را برمیگردونه .
        /// </summary>
        /// <param name="lpszVolumeMountPoint">
        /// نام درایو ای که GUID آنرا میخوایم . این نام ، باید با بک اسلش خاتمه پیدا کنه .
        /// </param>
        /// <param name="lpszVolumeName">
        /// مقدار GUID مربوط به درایوی را که در پارامتر lpszVolumeMountPoint مشخص کرده بودیم را بصورت خروجی به ما برمیگردونه .
        /// دقت کنید که این پارامتر ، خروجی هست .
        /// </param>
        /// <param name="cchBufferLength">
        /// طول بافر خروجی ، یعنی طول پارامتر رشته ی lpszVolumeName بر حسب TCHAR .
        /// اگر انکودینگ رشته ، بصورت ansi برای پارامتر lpszVolumeMountPoint در نظر گرفته بشه ، اندازه ی TCHAR برای هر کاراکتر ، یک بایت میشه وگرنه اگه unicode باشه ، اندازه ی هر کاراکتر براش 2 بایت در نظر گرفته میشه .
        /// اما با وجود طولانی ترین کاراکتر ، حداکثر 50 کاراکتر برای این پارامتر (یعنی 50 کاراکتر برای پارامتر دوم) کافی هست .
        /// </param>
        /// <returns>
        /// اگر متد ، موفقیت آمیز اجرا بشه ، مقدار بازگشتی ، غیر از صفر (یعنی true) وگرنه مقدار صفر (یعنی false) برگردونده میشه.
        /// </returns>
        [DllImport("Kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", CharSet = CharSet.Auto)]
        private static extern bool GetVolumeNameForVolumeMountPointW(string lpszVolumeMountPoint, out StringBuilder lpszVolumeName, int cchBufferLength);




        public static string GetVolumeGUID(this DriveInfo driveInfo)
        {
            StringBuilder lpszVolumeName = new StringBuilder(50);
            bool isSuccessCalling = ExtentionMethod.GetVolumeNameForVolumeMountPointW(driveInfo.Name, out lpszVolumeName, 50);
            MessageBox.Show(isSuccessCalling.ToString(), "GetVolumeGUID");
            return lpszVolumeName.ToString();
        }

    }

متد GetVolumeNameForVolumeMountPointW (که درون متد GetVolumeGUID هست) وقتی میخواد اجرا بشه ، ارور زیر رو میده :

کد:
System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'

ارور را میبره روی خط Application.Run (در متد Main) در کلاس Program .
واسه ی چی هه و چجوری باید رفع اش کرد؟
بجای StringBuilder در پارامتر دوم GetVolumeNameForVolumeMountPointW ، از string هم استفاده کردم اما همین ارور رو میده . باید از کلاس Marshal براش حافظه بگیرم تا جواب بده؟
ref هم کردم ، جواب نداد .
ref و out و ... مفاهیم داخل زبانی هستند. برای ارتباط با محیط Unmanaged که قراره Marshaling بشه [Out] ای بکار میره که معنی اش متفاوته. [Out] با out یکی نیست، شما با [Out] برای Marshaling راهنمایی ارائه می کنید که بدونه باید موقع بازگشت از متد داده حافظه مدیریت نشده رو داخل StringBuilder قرار بده. از طرف دیگه در اینکه CharSet.Unicode ئه شکی نیست، متد های W نمیتونن ANSI باشن.
نسخه ANSI اش GetVolumeNameForVolumeMountPointA ئه.
کد:
        [DllImport("Kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", CharSet = CharSet.Unicode)]
        private static extern bool GetVolumeNameForVolumeMountPointW(string lpszVolumeMountPoint, [Out] StringBuilder lpszVolumeName, int cchBufferLength);

        public static string GetVolumeGUID(this DriveInfo driveInfo)
        {
            var lpszVolumeName = new StringBuilder(50);
            var isSuccessCalling = ExtentionMethod.GetVolumeNameForVolumeMountPointW(driveInfo.Name, lpszVolumeName, 50);
            return (isSuccessCalling ? lpszVolumeName.ToString() : null);
        }
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
ref و out و ... مفاهیم داخل زبانی هستند. برای ارتباط با محیط Unmanaged که قراره Marshaling بشه [Out] ای بکار میره که معنی اش متفاوته. [Out] با out یکی نیست، شما با [Out] برای Marshaling راهنمایی ارائه می کنید که بدونه باید موقع بازگشت از متد داده حافظه مدیریت نشده رو داخل StringBuilder قرار بده. از طرف دیگه در اینکه CharSet.Unicode ئه شکی نیست، متد های W نمیتونن ANSI باشن.
نسخه ANSI اش GetVolumeNameForVolumeMountPointA ئه.
کد:
        [DllImport("Kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", CharSet = CharSet.Unicode)]
        private static extern bool GetVolumeNameForVolumeMountPointW(string lpszVolumeMountPoint, [Out] StringBuilder lpszVolumeName, int cchBufferLength);

        public static string GetVolumeGUID(this DriveInfo driveInfo)
        {
            var lpszVolumeName = new StringBuilder(50);
            var isSuccessCalling = ExtentionMethod.GetVolumeNameForVolumeMountPointW(driveInfo.Name, lpszVolumeName, 50);
            return (isSuccessCalling ? lpszVolumeName.ToString() : null);
        }

خیلی ممنون استاد .
آها [Out] ، اینجا در واقع ، اتریباتس هست . همون [OutAttributes] هه . اتریباتس ها ، به پارامترها هم میتونن نسبت داده بشن ؟ من فکر کردم فقط به متدها میتونن نسبت داده بشن .
درباره ی قضیه ی متدهای W دار هم ممنون.
بعد اینکه چرا کد بالا را وقتی با string انجام میدیم (string را هم با کاراکتر نال '\0' پر میکنیم) ، همه ی کاراکترهای استرینگ را مینویسه '\0' . اما همین کار را با StringBuilder انجام میدیم (هر چند کاراکتری براش در نظر نمیگیریم ولی کاراکتری اگه در نظر نگیریم ، حداقل باید نال یا همون '\0' را در نظر بگیره دیگه . درسته؟) ، مشکلی نیست؟ :

کد:
        [DllImport("Kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", CharSet = CharSet.Unicode)]
        private static extern bool GetVolumeNameForVolumeMountPointW(string lpszVolumeMountPoint, [OutAttribute()] string lpszVolumeName, int cchBufferLength);




        public static string GetVolumeGUID(this DriveInfo driveInfo)
        {
            string lpszVolumeName = new string('\0', 100);
            //StringBuilder lpszVolumeName = new StringBuilder(100);
            bool isSuccessCalling = ExtentionMethod.GetVolumeNameForVolumeMountPointW(driveInfo.Name, lpszVolumeName, 50);
            MessageBox.Show(isSuccessCalling.ToString(), "GetVolumeGUID");
            return lpszVolumeName.ToString();
        }

بعد اینکه استاد ، اگه ref و out واسه ی کدهای managed معنا داره و برای unmanaged معنا نداره ، پس شما چرا در کد زیر (که قبلا بهم داده بودین) ، از ref برای پارامتر آخر استفاده کردین؟ :

کد:
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "GetVolumePathNamesForVolumeNameW")]
        private static extern bool GetVolumePathNamesForVolumeNameW(string lpszVolumeName, string lpszVolumePathNames, int cchBuferLength, ref int lpcchReturnLength);
 
آخرین ویرایش:

the_king

مدیرکل انجمن
خیلی ممنون استاد .
آها [Out] ، اینجا در واقع ، اتریباتس هست . همون [OutAttributes] هه . اتریباتس ها ، به پارامترها هم میتونن نسبت داده بشن ؟ من فکر کردم فقط به متدها میتونن نسبت داده بشن .
درباره ی قضیه ی متدهای W دار هم ممنون.
بعد اینکه چرا کد بالا را وقتی با string انجام میدیم (string را هم با کاراکتر نال '\0' پر میکنیم) ، همه ی کاراکترهای استرینگ را مینویسه '\0' . اما همین کار را با StringBuilder انجام میدیم (هر چند کاراکتری براش در نظر نمیگیریم ولی کاراکتری اگه در نظر نگیریم ، حداقل باید نال یا همون '\0' را در نظر بگیره دیگه . درسته؟) ، مشکلی نیست؟ :

کد:
        [DllImport("Kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", CharSet = CharSet.Unicode)]
        private static extern bool GetVolumeNameForVolumeMountPointW(string lpszVolumeMountPoint, [OutAttribute()] string lpszVolumeName, int cchBufferLength);




        public static string GetVolumeGUID(this DriveInfo driveInfo)
        {
            string lpszVolumeName = new string('\0', 100);
            //StringBuilder lpszVolumeName = new StringBuilder(100);
            bool isSuccessCalling = ExtentionMethod.GetVolumeNameForVolumeMountPointW(driveInfo.Name, lpszVolumeName, 50);
            MessageBox.Show(isSuccessCalling.ToString(), "GetVolumeGUID");
            return lpszVolumeName.ToString();
        }
در استفاده از string برای API دقت کنید، رشته های داخل زبان ساختارشون فرق داره و مقدار بازگشتی شامل کاراکتر 0\ هم هست. این کاراکتر 0\ میتونه جلوی نمایش کاراکتر های بعدی رو بگیره.
تعریف string(char c, int) همینه که کاراکتر c به تعداد int تکرار بشه. اینکه داخلش چه کاراکتر هایی ثبت می کنید در این قضیه مهم نیست، مهم طول حافظه است.
متد GetVolumeNameForVolumeMountPointW کلا 50 امین کارکتر که برمیگردونه خود null ئه اما این باعث نمیشه که طول string تغییر کنه.
در رشته های C و ++C کاراکتر 0\ انتهای رشته رو مشخص میکنه و نمیشه جزئی از رشته باشه، اما در #C شما می توانید داخل خود رشته string تون هم کاراکتر 0\ قرار بدید و جزو طول رشته حساب میشه.
رشته های API ویندوز مثل C++ / C کلا با کاراکتر 0\ آخرش شناخته میشه، اینکه دیگه بعدش چه کاراکتر هایی ثبت شده اهمیتی نداره. اما برای اون پارامتر که به متد ارسال می کنید رشته صرفا خروجیه، دیگه محتوای ارسالی مهم نیست، طولش مهمه. چون فقط داده داخلش ثبت میشه، چیزی ازش خونده نمیشه. عیب کار با string در Marshaling اینه که شما گاهی مجبورید بعد از بازگشت متد کاراکتر null رو از داخلش دربیارید، چون طول رشته تغییر نمیکنه، فرضا string.Length تون بیشتر از کاراکتر های قابل نمایش شما است. در مثال قبلی خروجی StringBuilder رو بررسی کنید، 49 کاراکتر شده، 0\ آخرش رو نادیده گرفته و جزئی از ToString اش هم نیست.
اما خروجی string تون رو بررسی کنید، همون 50 اولیه است و کم نمیشه. موقع نمایش اش متوجه نمیشید چون قابل مشاهده نیست، ولی اگر با رشته های دیگه ترکیب اش کنید اون 0\ انتهایی دردسر میشه.
تفسیر Marshaling درست ئه، چون کاراکتر 50 ام 0\ ئه و در داخل StringBuilder خروجی نادیده گرفته میشه. ولی بازگشتی string فقط داده داخلش رو تغییر میده، طول رشته اولیه که مربوط به داخل زبان های NET. ئه تغییری نمی کنه، مقدار اولیه طول رو همچنان اعلام می کنه، چون string های #C کاری به موقعیت کاراکتر 0\ ندارند و طول رشته شون در جای دیگری مجزا ذخیره شده.
کد:
        public static string GetVolumeGUID(this DriveInfo driveInfo)
        {
            var lpszVolumeName = new string('?', 1000);
            var isSuccessCalling = ExtentionMethod.GetVolumeNameForVolumeMountPointW(driveInfo.Name, lpszVolumeName, 1000);
            return (isSuccessCalling ? lpszVolumeName.Substring(0, lpszVolumeName.IndexOf('\0')) : null);
        }
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
در استفاده از string برای API دقت کنید، رشته های داخل زبان ساختارشون فرق داره و مقدار بازگشتی شامل کاراکتر 0\ هم هست. این کاراکتر 0\ میتونه جلوی نمایش کاراکتر های بعدی رو بگیره.
تعریف string(char c, int) همینه که کاراکتر c به تعداد int تکرار بشه. اینکه داخلش چه کاراکتر هایی ثبت می کنید در این قضیه مهم نیست، مهم طول حافظه است.
متد GetVolumeNameForVolumeMountPointW کلا 50 امین کارکتر که برمیگردونه خود null ئه اما این باعث نمیشه که طول string تغییر کنه.
در رشته های C و ++C کاراکتر 0\ انتهای رشته رو مشخص میکنه و نمیشه جزئی از رشته باشه، اما در #C شما می توانید داخل خود رشته string تون هم کاراکتر 0\ قرار بدید و جزو طول رشته حساب میشه.
رشته های API ویندوز مثل C++ / C کلا با کاراکتر 0\ آخرش شناخته میشه، اینکه دیگه بعدش چه کاراکتر هایی ثبت شده اهمیتی نداره. اما برای اون پارامتر که به متد ارسال می کنید رشته صرفا خروجیه، دیگه محتوای ارسالی مهم نیست، طولش مهمه. چون فقط داده داخلش ثبت میشه، چیزی ازش خونده نمیشه. عیب کار با string در Marshaling اینه که شما گاهی مجبورید بعد از بازگشت متد کاراکتر null رو از داخلش دربیارید، چون طول رشته تغییر نمیکنه، فرضا string.Length تون بیشتر از کاراکتر های قابل نمایش شما است. در مثال قبلی خروجی StringBuilder رو بررسی کنید، 49 کاراکتر شده، 0\ آخرش رو نادیده گرفته و جزئی از ToString اش هم نیست.
اما خروجی string تون رو بررسی کنید، همون 50 اولیه است و کم نمیشه. موقع نمایش اش متوجه نمیشید چون قابل مشاهده نیست، ولی اگر با رشته های دیگه ترکیب اش کنید اون 0\ انتهایی دردسر میشه.
تفسیر Marshaling درست ئه، چون کاراکتر 50 ام 0\ ئه و در داخل StringBuilder خروجی نادیده گرفته میشه. ولی بازگشتی string فقط داده داخلش رو تغییر میده، طول رشته اولیه که مربوط به داخل زبان های NET. ئه تغییری نمی کنه، مقدار اولیه طول رو همچنان اعلام می کنه، چون string های #C کاری به موقعیت کاراکتر 0\ ندارند و طول رشته شون در جای دیگری مجزا ذخیره شده.
کد:
        public static string GetVolumeGUID(this DriveInfo driveInfo)
        {
            var lpszVolumeName = new string('?', 1000);
            var isSuccessCalling = ExtentionMethod.GetVolumeNameForVolumeMountPointW(driveInfo.Name, lpszVolumeName, 1000);
            return (isSuccessCalling ? lpszVolumeName.Substring(0, lpszVolumeName.IndexOf('\0')) : null);
        }

آها خیلی ممنون .
پس string ها در سی شارپ ، اولا میشه توشون نال ، یعنی '\0' را ذخیره کرد و دوما بر عکسِ سی پلاس پلاس که به کاراکتر نال میرسن ، همونجا را به عنوان پایان رشته میشناسن (و کاراکترِ نال را نمیتونن ذخیره یا حداقل نمایش بدن) ، string ها در سی شارپ ، کاراکترِ نال را به عنوان کاراکترِ آخر ، نمیشناسن .

بعد ، جواب اون خط آخری در پست بالا که ویرایش کرده بودم را هم میدین؟ :

بعد اینکه استاد ، اگه ref و out واسه ی کدهای managed معنا داره و برای unmanaged معنا نداره ، پس شما چرا در کد زیر (که قبلا بهم داده بودین) ، از ref برای پارامتر آخر استفاده کردین؟ :

کد:
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "GetVolumePathNamesForVolumeNameW")]
        private static extern bool GetVolumePathNamesForVolumeNameW(string lpszVolumeName, string lpszVolumePathNames, int cchBuferLength, ref int lpcchReturnLength);
 

the_king

مدیرکل انجمن
بعد اینکه استاد ، اگه ref و out واسه ی کدهای managed معنا داره و برای unmanaged معنا نداره ، پس شما چرا در کد زیر (که قبلا بهم داده بودین) ، از ref برای پارامتر آخر استفاده کردین؟ :

کد:
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "GetVolumePathNamesForVolumeNameW")]
        private static extern bool GetVolumePathNamesForVolumeNameW(string lpszVolumeName, string lpszVolumePathNames, int cchBuferLength, ref int lpcchReturnLength);
چون داده ای که بهش میخواستم بفرستم حقیقتا lpcchReturnLength نیست، آدرس اونه. توضیح متدش رو بخونید، قراره داخلش مقدار بنویسه. اگر بهش int بفرستید که عوضش کردنش تاثیری روی lpcchReturnLength نداره و نمیتونه عوضش کنه. آدرسش رو میخواد.
 

the_king

مدیرکل انجمن
آها خیلی ممنون .
پس string ها در سی شارپ ، اولا میشه توشون نال ، یعنی '\0' را ذخیره کرد و دوما بر عکسِ سی پلاس پلاس که به کاراکتر نال میرسن ، همونجا را به عنوان پایان رشته میشناسن (و کاراکترِ نال را نمیتونن ذخیره یا حداقل نمایش بدن) ، string ها در سی شارپ ، کاراکترِ نال را به عنوان کاراکترِ آخر ، نمیشناسن .

بعد ، جواب اون خط آخری در پست بالا که ویرایش کرده بودم را هم میدین؟ :

بعد اینکه استاد ، اگه ref و out واسه ی کدهای managed معنا داره و برای unmanaged معنا نداره ، پس شما چرا در کد زیر (که قبلا بهم داده بودین) ، از ref برای پارامتر آخر استفاده کردین؟ :

کد:
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "GetVolumePathNamesForVolumeNameW")]
        private static extern bool GetVolumePathNamesForVolumeNameW(string lpszVolumeName, string lpszVolumePathNames, int cchBuferLength, ref int lpcchReturnLength);
فرق PDWORD و DWORD همینه. DWORD یک داده 32 است ولی PDWORD اشاره گری است که به یک داده 32 بیتی اشاره میکنه.
کد:
BOOL GetVolumePathNamesForVolumeNameW(
  LPCWSTR lpszVolumeName,
  LPWCH   lpszVolumePathNames,
  DWORD   cchBufferLength,
  PDWORD  lpcchReturnLength
);
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
فرق PDWORD و DWORD همینه. DWORD یک داده 32 است ولی PDWORD اشاره گری است که به یک داده 32 بیتی اشاره میکنه.
کد:
BOOL GetVolumePathNamesForVolumeNameW(
  LPCWSTR lpszVolumeName,
  LPWCH   lpszVolumePathNames,
  DWORD   cchBufferLength,
  PDWORD  lpcchReturnLength
);

خیلی ممنون استاد
بله اینو میدونم .
منظورم این بود که بجای ref یا out ، از همین OutAttribute استفاده نکردین؟
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
متوجه سوال تون نمیشم. منظورتون اینه که آیا در اون کد Out ای هست که مخفی است و دیده نمیشه؟

منظورم اینه که در کد زیر :

کد:
[DllImport("Kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", CharSet = CharSet.Unicode)]
        private static extern bool GetVolumeNameForVolumeMountPointW(string lpszVolumeMountPoint, [OutAttribute()] StringBuilder lpszVolumeName, int cchBufferLength);

چرا از out (کلمه ی کلیدی سی شارپ) استفاده نمیکنیم و از اتریباتس OutAttribute استفاده میکنیم اما در کد زیر :

کد:
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "GetVolumePathNamesForVolumeNameW")]
        private static extern bool GetVolumePathNamesForVolumeNameW(string lpszVolumeName, string lpszVolumePathNames, int cchBuferLength, ref int lpcchReturnLength);

میتونیم از ref استفاده کنیم؟
اگه اینجا میشه از ref که کلمه ی کلیدی سی شارپ هست ، استفاده کرد ، پس در کد بالا هم باید بشه از out که کلمه ی کلیدی سی شارپ هست هم استفاده کرد (و از اتریباتس OutAttribute استفاده نکرد) . درسته؟
 

the_king

مدیرکل انجمن
منظورم اینه که در کد زیر :

کد:
[DllImport("Kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", CharSet = CharSet.Unicode)]
        private static extern bool GetVolumeNameForVolumeMountPointW(string lpszVolumeMountPoint, [OutAttribute()] StringBuilder lpszVolumeName, int cchBufferLength);

چرا از out (کلمه ی کلیدی سی شارپ) استفاده نمیکنیم و از اتریباتس OutAttribute استفاده میکنیم اما در کد زیر :

کد:
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "GetVolumePathNamesForVolumeNameW")]
        private static extern bool GetVolumePathNamesForVolumeNameW(string lpszVolumeName, string lpszVolumePathNames, int cchBuferLength, ref int lpcchReturnLength);

میتونیم از ref استفاده کنیم؟
اگه اینجا میشه از ref که کلمه ی کلیدی سی شارپ هست ، استفاده کرد ، پس در کد بالا هم باید بشه از out که کلمه ی کلیدی سی شارپ هست هم استفاده کرد (و از اتریباتس OutAttribute استفاده نکرد) . درسته؟
قبلا صحبتش رو کردیم که حافظه مدیریت شده و حافظه مدیریت نشده مجزا هستند و وقتی فرضا string یا int هر داده دیگری از #C به محیط مدیریت نشده منتقل میشه در یک حافظه مدیریت نشده کپی میشه و اگر بخواد داده ای از محیط مدیریت نشده به داخل متغیر های محیط مدیریت شده بیاد در یک حافظه مدیریت شده کپی میشه. شما هر کاری که بکنید یک بایت هم مستقیم بین ایندو محیط به اشتراک قرار داده نمیشه، در حافظه دیگری کپی میشه. بخشی اش رو خودتون دستی به کمک Marshal انجام میدید و بخشی اش رو Marshaling خودکار انجام میده.
شما اگر کدی بنویسید که Marshaling رو به اشتباه بندازه مقصر هستید.

ref int یعنی چی؟ یعنی میخوام مقداری که در پارامتر int نوشته میشه، مستقیما در همون حافظه ای نوشته بشه که موقع ارسال قرار داشت، برای همین اشاره گر به اون int ارسال میشه، نه مقدار int. و Marshaling هم از این ref int اینطور برداشت میکنه که اول بیاد یک حافظه مدیریت نشده به طول مناسب int ایجاد کنه، داده int رو داخلش کپی کنه، اشاره گر اون حافظه رو به متد بفرسته و وقتی متد اجرا شد int داخل حافظه مدیریت نشده رو کپی کنه در int اصلی. خوب این همون چیزی است که من میخواهم و مشکلی باهاش ندارم.
حالا ref StringBuilder یعنی چی؟ یعنی اشاره گری که به شی StringBuilder اشاره می کرد رو ارسال کن. چرا بد ئه؟ چون StringBuilder اساسا نوع داده ای نیست که متد بخواد. متد API که داده داخل StringBuilder رو نمیشناسه، یک نوع داده داخل NET. ای است. out StringBuilder هم همینطور بد ئه، ما از متد API که توقع نداریم بتونه برای ما شی StringBuilder بسازه. ما با Out] StringBuilder] مشخص می کنیم که میخواهیم تبادل داده توسط Marshaling مدیریت بشه و خودش StringBuilder رو به رشته تبدیل کنه.
ما فرضا میتوانیم از ref int و out int در خیلی موارد بجای هم استفاده کنیم، اما out string و ref string رو نمیشه بجای Out] string] بکار برد. Marshaling میدونه که باید Out] string] رو به یک رشته سازگار با API ویندوز تبدیل کنه، اما در مورد ref string و out string همچین برداشتی نداره.
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
قبلا صحبتش رو کردیم که حافظه مدیریت شده و حافظه مدیریت نشده مجزا هستند و وقتی فرضا string یا int هر داده دیگری از #C به محیط مدیریت نشده منتقل میشه در یک حافظه مدیریت نشده کپی میشه و اگر بخواد داده ای از محیط مدیریت نشده به داخل متغیر های محیط مدیریت شده بیاد در یک حافظه مدیریت شده کپی میشه. شما هر کاری که بکنید یک بایت هم مستقیم بین ایندو محیط به اشتراک قرار داده نمیشه، در حافظه دیگری کپی میشه. بخشی اش رو خودتون دستی به کمک Marshal انجام میدید و بخشی اش رو Marshaling خودکار انجام میده.
شما اگر کدی بنویسید که Marshaling رو به اشتباه بندازه مقصر هستید.

ref int یعنی چی؟ یعنی میخوام مقداری که در پارامتر int نوشته میشه، مستقیما در همون حافظه ای نوشته بشه که موقع ارسال قرار داشت، برای همین اشاره گر به اون int ارسال میشه، نه مقدار int. و Marshaling هم از این ref int اینطور برداشت میکنه که اول بیاد یک حافظه مدیریت نشده به طول مناسب int ایجاد کنه، داده int رو داخلش کپی کنه، اشاره گر اون حافظه رو به متد بفرسته و وقتی متد اجرا شد int داخل حافظه مدیریت نشده رو کپی کنه در int اصلی. خوب این همون چیزی است که من میخواهم و مشکلی باهاش ندارم.
حالا ref StringBuilder یعنی چی؟ یعنی اشاره گری که به شی StringBuilder اشاره می کرد رو ارسال کن. چرا بد ئه؟ چون StringBuilder اساسا نوع داده ای نیست که متد بخواد. متد API که داده داخل StringBuilder رو نمیشناسه، یک نوع داده داخل NET. ای است. out StringBuilder هم همینطور بد ئه، ما از متد API که توقع نداریم بتونه برای ما شی StringBuilder بسازه. ما با Out] StringBuilder] مشخص می کنیم که میخواهیم تبادل داده توسط Marshaling مدیریت بشه و خودش StringBuilder رو به رشته تبدیل کنه.
ما فرضا میتوانیم از ref int و out int در خیلی موارد بجای هم استفاده کنیم، اما out string و ref string رو نمیشه بجای Out] string] بکار برد. Marshaling میدونه که باید Out] string] رو به یک رشته سازگار با API ویندوز تبدیل کنه، اما در مورد ref string و out string همچین برداشتی نداره.

خیلی ممنون استاد.
متوجه شدم ولی دقیق دقیق شاید نه .
الان چرا بجای اون کد ، کد زیر را بنویسیم ، ارور میده؟ (فقط تغییرات را مینویسم) :

کد:
        private static extern bool GetVolumeNameForVolumeMountPointW(string lpszVolumeMountPoint, ref string lpszVolumeName, int cchBufferLength);




            string lpszVolumeName = new string('\0', 100);
            bool isSuccessCalling = ExtentionMethod.GetVolumeNameForVolumeMountPointW(driveInfo.Name, ref lpszVolumeName, 50);
 

the_king

مدیرکل انجمن
خیلی ممنون استاد.
متوجه شدم ولی دقیق دقیق شاید نه .
الان چرا بجای اون کد ، کد زیر را بنویسیم ، ارور میده؟ (فقط تغییرات را مینویسم) :

کد:
        private static extern bool GetVolumeNameForVolumeMountPointW(string lpszVolumeMountPoint, ref string lpszVolumeName, int cchBufferLength);




            string lpszVolumeName = new string('\0', 100);
            bool isSuccessCalling = ExtentionMethod.GetVolumeNameForVolumeMountPointW(driveInfo.Name, ref lpszVolumeName, 50);
مساله رو بدون در نظر گرفتن Marshaling بررسی کنیم که ساده تر معلوم بشه. یعنی حافظه مدیریت شده و نشده و کپی شدن بین ایندو رو در نظر نگیریم.
فرض کنیم رشته lpszVolumeName در آدرس حافظه 1000 به بعد ذخیره شده باشه. وقتی با Out] string] ارسال میشه، مقدار 1000 با پارامتر ارسال میشه. متد اون 1000 رو میگیره و میدونه آدرس رشته 1000 ئه.
مقادیر داخل خونه های حافظه 1000 به بعد رو میخونه یا تغییر میده. وقتی اجرای متد تموم شد و خواستیم lpszVolumeName رو استفاده کنیم تغییرات متد داخلش هست. چون در خونه های 1000 به بعد مقادیری قرار داره که متد نوشته.

اما وقتی شما ref string رو بکار می برید میایید یک اشاره گر اضافی رو تحمیل می کنید. در یک آدرس از حافظه مقدار 1000 قرار میگیره و آدرس اون خونه از حافظه که داخلش 1000 بود ارسال میشه به متد. یعنی خود 1000 ارسال نمیشه. اشاره گری به اون مقدار 1000 ارسال میشه که خود 1000 اش هم اشاره گر بود. متدی میتونه از همچین پارامتری مثل ref string استفاده کنه که بدونه رشته در اون آدرسی که میگیره نیست، بلکه رشته در آدرسی است که آدرس اون آدرس در اون پارامتر مشخص شده. متدی که فکر میکنه رشته در اون آدرس هست با خطا روبرو میشه، چون آدرس معتبری برای رشته نداره. آدرسی رو برای رشته داره که فقط یک مقدار 32/64 بیتی داخلش هست و خانه های بعدی برای دسترسی اش معتبر نیستند، اونجا بهش رجوع کرده یا میخواد داخلش رشته رو ثبت کنه خبری از یک رشته نیست و جا برای اون 50 کاراکتر رو هم نداره. تازه اگر بنویسه و خطا هم نده در بازگشت از متد قابل استفاده نیست و رشته اصلی دست نخورده مونده، فقط آدرس اشاره گر اش که 1000 بوده در جای دیگری از حافظه بازنویسی و تخریب شده. 1000 شده یک چیز دیگری که ربطی هم به lpszVolumeName نداره.
در زبان ++C اشاره گر ها با * تعریف میشن، فرضا *char اشاره گری به char ئه. ref string چیزی مثل **char میمونه، یعنی اشاره گری که *char اشاره می کنه. یا به عبارت دیگه اشاره گری که به یک اشاره گر اشاره می کنه که به char اشاره میکنه.
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
مساله رو بدون در نظر گرفتن Marshaling بررسی کنیم که ساده تر معلوم بشه. یعنی حافظه مدیریت شده و نشده و کپی شدن بین ایندو رو در نظر نگیریم.
فرض کنیم رشته lpszVolumeName در آدرس حافظه 1000 به بعد ذخیره شده باشه. وقتی با Out] string] ارسال میشه، مقدار 1000 با پارامتر ارسال میشه. متد اون 1000 رو میگیره و میدونه آدرس رشته 1000 ئه.
مقادیر داخل خونه های حافظه 1000 به بعد رو میخونه یا تغییر میده. وقتی اجرای متد تموم شد و خواستیم lpszVolumeName رو استفاده کنیم تغییرات متد داخلش هست. چون در خونه های 1000 به بعد مقادیری قرار داره که متد نوشته.

اما وقتی شما ref string رو بکار می برید میایید یک اشاره گر اضافی رو تحمیل می کنید. در یک آدرس از حافظه مقدار 1000 قرار میگیره و آدرس اون خونه از حافظه که داخلش 1000 بود ارسال میشه به متد. یعنی خود 1000 ارسال نمیشه. اشاره گری به اون مقدار 1000 ارسال میشه که خود 1000 اش هم اشاره گر بود. متدی میتونه از همچین پارامتری مثل ref string استفاده کنه که بدونه رشته در اون آدرسی که میگیره نیست، بلکه رشته در آدرسی است که آدرس اون آدرس در اون پارامتر مشخص شده. متدی که فکر میکنه رشته در اون آدرس هست با خطا روبرو میشه، چون آدرس معتبری برای رشته نداره. آدرسی رو برای رشته داره که فقط یک مقدار 32/64 بیتی داخلش هست و خانه های بعدی برای دسترسی اش معتبر نیستند، اونجا بهش رجوع کرده یا میخواد داخلش رشته رو ثبت کنه خبری از یک رشته نیست و جا برای اون 50 کاراکتر رو هم نداره. تازه اگر بنویسه و خطا هم نده در بازگشت از متد قابل استفاده نیست و رشته اصلی دست نخورده مونده، فقط آدرس اشاره گر اش که 1000 بوده در جای دیگری از حافظه بازنویسی و تخریب شده. 1000 شده یک چیز دیگری که ربطی هم به lpszVolumeName نداره.
در زبان ++C اشاره گر ها با * تعریف میشن، فرضا *char اشاره گری به char ئه. ref string چیزی مثل **char میمونه، یعنی اشاره گری که *char اشاره می کنه. یا به عبارت دیگه اشاره گری که به یک اشاره گر اشاره می کنه که به char اشاره میکنه.

آها خیلی ممنون استاد .
پس در واقع وقتی مینویسیم ref ، اشاره گری به اون حافظه رو ارسال میکنه . حالا اگه خود اون نوع داده ای ، اشاره گر باشه ، مشخص هست که اشاره گرِ به اون اشاره گر را ارسال میکنه (در اینجا string که اشاره گر هست پس ref string ، اشاره گرِ به اشاره گرِ رشته را ارسال میکنه) .

من فکر میکردم out و ref ، هر دو یه کار را میکنن . فقط وقتی ref مینویسیم ، بعد از ارسال به متد ، داخل متد ، اگه هر شی جدیدی (که آدرس متفاوتی داره) برای متغییری که با ref مشخص کردیم ، در نظر بگیریم ، آدرس متغییر اصلی ای که (قبل از ارسال به متد) با ref مشخص کرده بودیم را هم تغییر میده . الان این که گفتم ، درسته دیگه؟ یعنی ref ، علاوه بر اینکه شما اون چیزی که گفتین (ارسال اشاره گرِ به اون شی) رو انجام میده ، این رو هم انجام میده دیگه؟
 

the_king

مدیرکل انجمن
آها خیلی ممنون استاد .
پس در واقع وقتی مینویسیم ref ، اشاره گری به اون حافظه رو ارسال میکنه . حالا اگه خود اون نوع داده ای ، اشاره گر باشه ، مشخص هست که اشاره گرِ به اون اشاره گر را ارسال میکنه (در اینجا string که اشاره گر هست پس ref string ، اشاره گرِ به اشاره گرِ رشته را ارسال میکنه) .

من فکر میکردم out و ref ، هر دو یه کار را میکنن . فقط وقتی ref مینویسیم ، بعد از ارسال به متد ، داخل متد ، اگه هر شی جدیدی (که آدرس متفاوتی داره) برای متغییری که با ref مشخص کردیم ، در نظر بگیریم ، آدرس متغییر اصلی ای که (قبل از ارسال به متد) با ref مشخص کرده بودیم را هم تغییر میده . الان این که گفتم ، درسته دیگه؟
در مورد ref داخل زبان #C درسته ولی در مورد متد خارجی نه. اولا معمولا همچین اتفاقی نمی افته چون مدیریت اون شیء ایجاد شده و حافظه اش رو باید کسی به عهده بگیره. اگر بخواد از متد به خارج از محیط کتابخانه منتقل بشه نمیتونه موقع خروج از متد نابود بشه و نابود کردنش باید با طرف مشخصی باشه. ثانیا متد که نمیدونه پارامتر ارسالی با ref ارسال شده یا out یا هر چیز دیگری. صرفا یک اشاره گر دریافت کرده.
شما اگر در یک متد داخل زبان هم فرضا پارامتر ورودی IntPtr رو تغییر بدید بیرون متد که متوجه تغییر اون پارامتر نمیشن، چون مثل متغیر های محلی موقتی است. اون اشاره گری که به متد خارجی میفرستید هم همینطوره، اگر متد تغییرش بده در محیط خارج تاثیری نداره. مثل متغیر محلی خود متد باهاش رفتار میشه.
اگر متد بخواد بجای شیء ای که در آدرس مورد نظر قرار داره شیء جدیدی با آدرس متفاوتی رو بکار ببره که دیگه استفاده ای از اشاره گر ارسال شده نکرده و تاثیری روی شیء ارسالی نداره و تغییری هم در طرف ارسال کننده نخواهد داشت. در اغلب سیستم های ارسال پارامتر به متد مثل stdcall که API ویندوز هم ازش استفاده میکنه از پشته (stack) استفاده میشه. موقع ارسال پارامتر ها در پشته قرار میگیرند و موقع خروج از متد از پشته درمیان. Marshaling حواسش به مقادیر داخل پشته نیست، چون مقادیر پارامتر ها داخل پشته نمیمونه. بعد از خروج از متد مقادیر از پشته در اومده. چیزی داخل پشته نمونده که Marshaling بررسی اش کنه. Marshaling حواسش به آدرس شیء ای که ارسال کرده هست، نه آدرس اشاره گری که ارسال کرده. اگه در آدرس دیگری توسط متد مقداری ثبت بشه که Marshaling اهمیتی نمیده. Marshaling حواسش به حافظه ای است که خودش برای کپی کردن از حافظه مدیریت شده میسازه. عملا اگر متد شی جدیدی در اون پارامتر داخل پشته قرار بده (که میدونیم یک اشاره گر ئه)، یک پارامتر دریافت کرده که محلش نذاشته و نادیده گرفتتش، بعد از خروج از متد هم اشاره گر از پشته در میاد و میره پی کارش.

یعنی ref ، علاوه بر اینکه شما اون چیزی که گفتین (ارسال اشاره گرِ به اون شی) رو انجام میده ، این رو هم انجام میده دیگه؟
دقت کنید که ref یک کلمه کلیدی داخل زبانی ئه و ارتباط با محیط unmanaged هم وظیفه ref و out و ... نیست. شما دارید در مورد کارهایی که Marshaling انجام میده حرف میزنید که قبلا توضیح دادم چیکار میکنه.
Marshaling بر اساس کلمات کلیدی داخل زبان قضاوت هایی میکنه اما وظیفه خودش رو به عهده ref یا out یا [Out] نمیسپاره.
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
ابهام تون خاصه و عجیبه البته. بذارید مرحله به مرحله بیایم جلو، ببینیم ابهام تون مربوط به کدوم بخش مساله است.
شما در اینکه در کد پایین مقدار a به 2 تغییر پیدا نمی کنه و a همچنان 1 میمونه که مشکلی ندارید؟
کد:
            int a = 1;
            int b = a;
            b = 2;
پس قاعدتا با اینکه در کد پایین مقدار a به (2,2) تغییر پیدا نمی کنه و همچنان (1,1) میمونه هم مشکلی ندارید :
کد:
            Point a = new Point(1, 1);
            Point b = a;
            b.X = 2;
            b.Y = 2;
و در اینکه در کد پایین هم مقدار a همچنان (1,1) میمونه مشکلی ندارید :
کد:
        private Point a = new Point(1, 1);

        private Point GetA()
        {
            return a;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Point b = GetA();
            b.X = 2;
            b.Y = 2;
        }
و در اینکه در کد پایین هم مقدار a همچنان (1,1) میمونه مشکلی ندارید :
کد:
        private Point a = new Point(1, 1);

        private Point A
        {
            get
            {
                return a;
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Point b = A;
            b.X = 2;
            b.Y = 2;
        }
و مشکلی با اینکه در این کد پایین مقدار c همون (1,1) خواهد بود ندارید :
کد:
        private Point a = new Point(1, 1);

        private Point A
        {
            get
            {
                return a;
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Point b = A;
            b.X = 2;
            b.Y = 2;
            Point c = A;
        }
پس قاعدتا با این مساله هم مشکلی ندارید که در get ئه Location یک کپی از مقدار loc ئه return میشه و هر بلایی سر مقدار کپی شده بیارید، تاثیری روی خود loc و مقدارش نداره.
Location + .X + = 4 سه بخشه، اول Location. ارزیابی میشه که میاد یک کپی از مقدار loc رو بر می گردونه. بعد X. ارزیابی میشه، به X اون مقدار کپی شده دسترسی پیدا می کنه که میتونه خونده یا نوشته بشه. بعد 4 = ارزیابی میشه که میخواد مقدار X رو در اون مقدار کپی شده تغییر بده، از نظر فنی عملی ئه ولی این مقدار کپی شده اصلا کجا است؟ یک جای نامعلوم از حافظه که متغیری هم برای دسترسی بهش ندارید. شما بعدا بخواهید ازش استفاده کنید کجا دنبالش می گردید؟ در Location که نمی تونید بگردید، چون هر بار مقدار loc رو return می کنه و چون loc همونه که قبلا بود و X اش هم با اینکارا عوض نمیشه کارتون بیهوده است. شما هر بلایی سر Location.X و Location.Y بیارید هیچ تغییری روی loc صورت نمی گیره، چون از Location فقط کپی مقدار loc ئه که return میشه. خودش عوض نمیشه که. گیرم که شما مقدار X اش رو به 4 تغییر بدید. بعدش چی؟ این مقدار کپی شده کجا است که ازش استفاده کنید؟ هیچ جا. مثل شیء ای است که ساخته باشید ولی چون در هیچ متغیری قرارش نداده اید دیگه بهش دسترسی ندارید و از دست میره. Location.X = 4 یک تغییر لحظه ای ئه که نتیجه اش در یک حافظه موقتی ثبت میشه و بعد از دست میره. هیچ تاثیری هم روی loc و مقدارش نمیذاره.
کامپایلر به این جهت جلوی اینکار رو می گیره که کاری که می کنید بیخودیه، منطق درستی نداره. همونطور که جلوی ;int i; int i رو میگیره، تعریف مجدد i به همون شکل قبلی مشکلی ایجاد نمی کنه، اما چون بیخودیه با خطا جلوشو می گیره.

سلامی مجدد استاد .
خیلی ممنون استاد . هم بابت پست بالا (یه دور خوندم . حالا بذارید بیشتر بخونم) و هم بابت این پست قدیمی تون که نقل قول کردم .
(توی پرانتز اینو بگم . استاد ، یه کم شوخی کنیم ، فضا شاد بشه . :green: من کلا توی فیلم آموزشی ام ، شوخی میکنم ، آدم بخندن ، بهتر یاد میگیرن . شاداب تر میشن :green: تازه ، ساز و نواز هم میذارم :green: )
استاد ، میگم این پست را که نقل قول کردم ، برداشتم ، اگه این جوری ای که در زیر توضیح میدم باشه ، درسته؟ (البته قبلا در همون صفحه ، برداشتمو گفتم ولی حس میکنم یه چیزهایی کم داره که فکر کنم توضیح زیر ، درست اش باشه) :

در کد زیر :

کد:
        private Point _a = new Point(1, 1);

        private Point A
        {
            get
            {
                return this._a;
            }
            set
            {
                this._a = value;
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Point b = this.A;
            b = new Point(3, 5);
        }

وقتی کدمون در رویداد Form1_Load اجرا میشه ، پروپرتیِ A ، باید خونده بشه و برای این کار ، مشخص هست که قسمت get پروپرتی باید اجرا بشه و چون قسمت get اش یک استراکچر را برمیگردونه (یا در واقع بهتره بگم که چون این پروپرتی از نوع استراکچر هست ، پس موقع برگردوندن استراکچر) ، مقدار این استراکچر را در حافظه ی خودش (که در اینجا متغییر b باشه) ، کپی میکنه . (نکته ی قابل توجه و البته بدیهی ، اینکه اگه متغییری مشخص نکرده باشیم ، یعنی در اینجا متغییر b را ننوشته باشیم و پروپرتی A را فراخونی کرده باشیم (ربطی به اعضای پروپرتی نداره) ، این مقدار متغییر a_ ، در حافظه ی بدون دسترس ای کپی میشه) . یعنی علاوه بر اینکه متغییر a_ ، مقدار 1 و 1 را خواهد داشت ، متغییر b ، در خط اول ، یک کپی از اون مقدار 1 و 1 را خواهد داشت و در خط دوم ، مقدار متغییر b ، به 3 و 5 تغییر میکنه اما مقدار متغییر a_ ، همون 1 و 1 باقی میمونه .
حالا نمیدونم پروپرتی هم حافظه داره یا نه (باید داشته باشه . درسته؟) و وقتی قسمت get اش فراخونی میشه ، اون مقدار را توی خودِ حافظه ی پروپرتیهم کپی میکنه یا نه؟ باید بکنه ، نه؟

وقتی هم که کد خط زیر اجرا میشه :

کد:
        private void Form1_Load(object sender, EventArgs e)
        {
            this.A = new Point(5, 10);
        }

قسمت set پروپرتی اجرا و مقدار 5 , 10 را در متغییر a_ کپی میکنه .

حالا نکته ی مهم (قضیه ی همین موضوع this.Location.X) اینه که در کد زیر :

کد:
        private void Form1_Load(object sender, EventArgs e)
        {
            this.A.X = 7;
        }

در کد بالا ، وقتی تا کد this.A فراخونی میشه ، قسمت get در پروپرتیِ A فراخونی میشه . بنابراین مقدار 1 و 1 در این پروپرتی ، کپی میشه (بنابراین تا اینجا ، مقدار this.A.X برابر با 1 هست) . وقتی که مقدار this.Location.X را تغییر میدیم ، نکته ی مهم اینجاست که مقدار x از پروپرتیِ A (که تا به حال ، مقدارش 1 بود) ، به 7 تغییر میکنه . این در حالی هه که با تغییر عضوی از پروپرتی (یعنی تغییر عضو X از پروپرتیِ A) ، قسمتِ Set در اون پروپرتی اصلا اجرا نمیشه (شبیه همون قضیه ای که در پست 1374 و 1375 و ... جواب ام را دادید و گفتید باید درون متد UITypeEditor.EditValue ، برای پروپرتیِ TransparentControlBitmap ، شی جدیدی در نظر بگیرم نه اینکه فقط برای اعضای این پروپرتی (مثل عضو DefaultBitmap و ...) شی جدیدی بسازم) :

گفتگو هایی در باب سی شارپ

(حالا میفهمم چرا قبلا گفتم ، این اطلاعات ، توی ضمیر ناخودآگاهم بود و بصورت واضح ، نمیدونستم :green: )
بنابراین وقتی قسمت set اجرا نشه ، مقدار پروپرتیِ A را که تغییر پیدا کرده را در متغییر a_ نمیریزه (بنابراین ، متغییر a_ ، همون مقدار 1 ای که قبلا داشت را هنوز داره و به مقدارِ this.A.X که 7 هست ، تغییر نیافت) . باز این در حالی هه که مقدار را توی a_ نریخت اما در دفعات بعد ، وقتی پروپرتی this.A فراخونی میشه ، قسمت و اکسسور get در این پروپرتی اجرا میشه که مقدار متغییر a_ را که همون مقدار قبلی ، یعنی 1 بود را میخونه .
بنابراین به همین دلیل هست که نمیذارن در پروپرتی از نوع استراکچر ، اعضای اون را تغییر بدیم (چون زمان set کردن در اعضای اون پروپرتی ، تغییراتش درون اون متغییر ، اِعمال نمیشه) .
درسته؟


--------------------------------------------------------------------------------------------------------------------

بعد میخواستم درباره ی این پست ای که در تاپیک آموزش سی شارپ نوشتم ، بررسی کنید که درسته یا نه (چون تا جایی که میدونم ، نباید مشکلی باشه) ، که لطف کردین جواب دادین . حالا من برم پست تون را بخونم (خیلی ممنون) :

دانلود فیلم آموزش کامل برنامه نویسی Visual Studio C#.NET

--------------------------------------------------------------------------------------------------------------------

نکته ی بعدی اینکه استاد ، من توی آزمون شرکت های خصوصی شرکت کردم (حالا گفتم شرکت را کنم) (رشته ی سی شارپ را انتخاب کردم) . گفتن نیمه ی دوم آبان ، امتحان تخصصی هه . شما میدونین از کجاها امتحان میاد و چه چیزهایی را باید بیشتر بخونم؟

خیلی ممنون استاد .
 
آخرین ویرایش:

the_king

مدیرکل انجمن
وقتی کدمون در رویداد Form1_Load اجرا میشه ، پروپرتیِ A ، باید خونده بشه و برای این کار ، مشخص هست که قسمت get پروپرتی باید اجرا بشه و چون قسمت get اش یک استراکچر را برمیگردونه (یا در واقع بهتره بگم که چون این پروپرتی از نوع استراکچر هست ، پس موقع برگردوندن استراکچر) ، مقدار این استراکچر را در حافظه ی خودش (که در اینجا متغییر b باشه) ، کپی میکنه . (نکته ی قابل توجه و البته بدیهی ، اینکه اگه متغییری مشخص نکرده باشیم ، یعنی در اینجا متغییر b را ننوشته باشیم و پروپرتی A را فراخونی کرده باشیم (ربطی به اعضای پروپرتی نداره) ، این مقدار متغییر a_ ، در حافظه ی بدون دسترس ای کپی میشه) . یعنی علاوه بر اینکه متغییر a_ ، مقدار 1 و 1 را خواهد داشت ، متغییر b ، در خط اول ، یک کپی از اون مقدار 1 و 1 را خواهد داشت و در خط دوم ، مقدار متغییر b ، به 3 و 5 تغییر میکنه اما مقدار متغییر a_ ، همون 1 و 1 باقی میمونه .
حالا نمیدونم پروپرتی هم حافظه داره یا نه (باید داشته باشه . درسته؟) و وقتی قسمت get اش فراخونی میشه ، اون مقدار را توی خودِ حافظه ی پروپرتیهم کپی میکنه یا نه؟ باید بکنه ، نه؟
نه، حافظه داده ای نداره. Property ها شبیه متغیر ها نیستند، در حقیقت متد هستند، قبلا گفتم فرضا A عملا متد get_A میشه که فراخوانی خواهد شد و مقداری برمیگردونه. نه اینکه یک متغیری شبیه prop_A باشه که مقدار داخلش کپی بشه و یا مقدار ازش خونده بشه.

وقتی هم که کد خط زیر اجرا میشه :

کد:
        private void Form1_Load(object sender, EventArgs e)
        {
            this.A = new Point(5, 10);
        }

قسمت set پروپرتی اجرا و مقدار 5 , 10 را در متغییر a_ کپی میکنه .

حالا نکته ی مهم (قضیه ی همین موضوع this.Location.X) اینه که در کد زیر :

کد:
        private void Form1_Load(object sender, EventArgs e)
        {
            this.A.X = 7;
        }

در کد بالا ، وقتی تا کد this.A فراخونی میشه ، قسمت get در پروپرتیِ A فراخونی میشه . بنابراین مقدار 1 و 1 در این پروپرتی ، کپی میشه (بنابراین تا اینجا ، مقدار this.A.X برابر با 1 هست) . وقتی که مقدار this.Location.X را تغییر میدیم ، نکته ی مهم اینجاست که مقدار x از پروپرتیِ A (که تا به حال ، مقدارش 1 بود) ، به 7 تغییر میکنه . این در حالی هه که با تغییر عضوی از پروپرتی (یعنی تغییر عضو X از پروپرتیِ A) ، قسمتِ Set در اون پروپرتی اصلا اجرا نمیشه (شبیه همون قضیه ای که در پست 1374 و 1375 و ... جواب ام را دادید و گفتید باید درون متد UITypeEditor.EditValue ، برای پروپرتیِ TransparentControlBitmap ، شی جدیدی در نظر بگیرم نه اینکه فقط برای اعضای این پروپرتی (مثل عضو DefaultBitmap و ...) شی جدیدی بسازم) :

گفتگو هایی در باب سی شارپ

(حالا میفهمم چرا قبلا گفتم ، این اطلاعات ، توی ضمیر ناخودآگاهم بود و بصورت واضح ، نمیدونستم :green: )
بنابراین وقتی قسمت set اجرا نشه ، مقدار پروپرتیِ A را که تغییر پیدا کرده را در متغییر a_ نمیریزه (بنابراین ، متغییر a_ ، همون مقدار 1 ای که قبلا داشت را هنوز داره و به مقدارِ this.A.X که 7 هست ، تغییر نیافت) . باز این در حالی هه که مقدار را توی a_ نریخت اما در دفعات بعد ، وقتی پروپرتی this.A فراخونی میشه ، قسمت و اکسسور get در این پروپرتی اجرا میشه که مقدار متغییر a_ را که همون مقدار قبلی ، یعنی 1 بود را میخونه .
بنابراین به همین دلیل هست که نمیذارن در پروپرتی از نوع استراکچر ، اعضای اون را تغییر بدیم (چون زمان set کردن در اعضای اون پروپرتی ، تغییراتش درون اون متغییر ، اِعمال نمیشه) .
درسته؟
تفسیرتون رو نمی پسندم چون به value type بودن نوع داده Point که مساله اساسی و دلیل قضیه است در تفسیر تون توجهی نمی کنید. فرض کنیم a_ و A از نوع یک کلاس بودند، نه یک struct :
کد:
        public class CPoint
        {
            public int X
            {
                get; set;
            }
            public int Y
            {
                get; set;
            }
        }
وقتی شما this.A.X = 7 رو اجرا می کنید ابتدا باید this.A بدست بیاد و بعد در X. اش مقدار 7 نوشته بشه. this.A چون از نوع یک reference type ئه this.A همون a_ رو بدست میاره، نه یک کپی از a_
و شما عملا دارید a.X = 7_ رو اجرا می کنید. به همین جهت اگر وقتی با یک reference type طرف هستید تغییر دادن فیلد های داخل شیء از طریق مشخصه ها میسر ئه.

اما در مثال شما A و a_ یک value type ئه. وقتی شما this.A = 7 رو اجرا می کنید ابتدا this.A برای شما کپی میشه در جای دیگری و شما کپی اون a_ رو دریافت می کنید. در ادامه وقتی X رو تغییر میدید، مثلا 7 داخلش ثبت می کنید در اون نسخه کپی X جدید ثبت میشه، نه در a_. طبیعی است که دفعات بعدی this.A همچنان یک کپی از a_ دست نخورده رو به شما تحویل بدهند. شما هیچوقت از طریق this.A نمی توانید خود a_ را دریافت کنید، چون value type ئه، همیشه یک کپی دریافت می کنید. برای همینه که نسخه کپی رو ویرایش می کنید و نسخه اصلی تغییری نمی کنه.

نکته ی بعدی اینکه استاد ، من توی آزمون شرکت های خصوصی شرکت کردم (حالا گفتم شرکت را کنم) (رشته ی سی شارپ را انتخاب کردم) . گفتن نیمه ی دوم آبان ، امتحان تخصصی هه . شما میدونین از کجاها امتحان میاد و چه چیزهایی را باید بیشتر بخونم؟
نه، اطلاعی ندارم. تجربه پرسیدن در مورد نمونه کار و پروژه های قبلی رو داشتم ولی تا حالا ازم آزمون برنامه نویسی نگرفتن. اگر کارشون اصولی باشه ازتون نمیپرسن که فرضا Thread.Sleep چیکار میکنه. صورت مساله ای رو میدن تا به روش خودتون برایش راه حل بدید، یعنی ممکنه سوال یک جواب واحد نداشته باشه و هر کسی به روش خودش حل اش کنه، هدف باید این باشه که ببینند توانایی حل مسائل رو دارید، نه اینکه دستورات #C و کتابخانه های NET. رو میشناسید.
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
نه، حافظه داده ای نداره. Property ها شبیه متغیر ها نیستند، در حقیقت متد هستند، قبلا گفتم فرضا A عملا متد get_A میشه که فراخوانی خواهد شد و مقداری برمیگردونه. نه اینکه یک متغیری شبیه prop_A باشه که مقدار داخلش کپی بشه و یا مقدار ازش خونده بشه.


تفسیرتون رو نمی پسندم چون به value type بودن نوع داده Point که مساله اساسی و دلیل قضیه است در تفسیر تون توجهی نمی کنید. فرض کنیم a_ و A از نوع یک کلاس بودند، نه یک struct :
کد:
        public class CPoint
        {
            public int X
            {
                get; set;
            }
            public int Y
            {
                get; set;
            }
        }
وقتی شما this.A.X = 7 رو اجرا می کنید ابتدا باید this.A بدست بیاد و بعد در X. اش مقدار 7 نوشته بشه. this.A چون از نوع یک reference type ئه this.A همون a_ رو بدست میاره، نه یک کپی از a_
و شما عملا دارید a.X = 7_ رو اجرا می کنید. به همین جهت اگر وقتی با یک reference type طرف هستید تغییر دادن فیلد های داخل شیء از طریق مشخصه ها میسر ئه.

خیلی ممنون استاد .
آها پس پروپرتی (وقتی یه مقداری را برمیگردونه و ...) ، دقیق برابر با متد میشه . وقتی توی پروپرتی ، کدی را مینویسیم ، انگار توی متد ، کدی را نوشتیم یا برگردوندیم . بله قبلا گفته بودید .
به value type بودن ، توجه کردم ها . توی توضیح هم گفتم .

اما در مثال شما A و a_ یک value type ئه. وقتی شما this.A = 7 رو اجرا می کنید ابتدا this.A برای شما کپی میشه در جای دیگری و شما کپی اون a_ رو دریافت می کنید. در ادامه وقتی X رو تغییر میدید، مثلا 7 داخلش ثبت می کنید در اون نسخه کپی X جدید ثبت میشه، نه در a_. طبیعی است که دفعات بعدی this.A همچنان یک کپی از a_ دست نخورده رو به شما تحویل بدهند. شما هیچوقت از طریق this.A نمی توانید خود a_ را دریافت کنید، چون value type ئه، همیشه یک کپی دریافت می کنید. برای همینه که نسخه کپی رو ویرایش می کنید و نسخه اصلی تغییری نمی کنه.

الان استاد ، شما میگین ، وقتی کد زیر اجرا میشه (A ، همونطور که کدش را در پست بالا دادم ، یک پروپرتی هست) ، با اونکه پروپرتیِ A مقداردهی میشه (بنابراین مشخص هست که اکسسور set در این پروپرتی اجرا میشه) ، قبل از اکسسور set ، مقدارش فراخونی میشه ؟
یعنی قبل از اینکه این پروپرتی ، مقداردهی بشه (قبل از اینکه اکسسور set اش اجرا بشه) ، مقدارش فراخونی میشه (اکسسور get اش فراخونی میشه) و بعد اکسسور set اش اجرا میشه؟

کد:
this.A = new Point(5, 10);

اگه منظورتون اینه ، من فکر نکنم این جوری باشه (حتما من منظورتون را بد متوجه شدم). چون موقع مقداردهی ، فقط باید اکسسور set اش اجرا بشه . نیازی نداره که مقدار رو get کنه . مقدارش هر چقدر که بود ، بود . براش قطعا نباید اهمیت داشته باشه چون مقدار جدیدی براش میخواد در نظر گرفته بشه توی break point هم همچین چیزی را نشون نمیده (در کد بالا) .
مثلا در کد زیر :

کد:
this.A.X = 5;

قبول دارم که وقتی تا this.A اجرا شد ، اول میره قسمت get در پروپرتی A را اجرا میکنه تا مقدارش را کپی کنه توی حافظه ی جدیدش تا به عضوِ X در اون استراکچر ، دسترسی پیدا کنه تا بتونه مقدار اون عضو را تغییر بده (هر چند ، در حافظه ی جدید) ولی اون کد خط بالاتر رو قبول ندارم که وقتی شی ای ست میکنیم ، بره اکسسورِ get در پروپرتی را اجرا کنه .

بجز این مطلبی که گفتم ، بقیه ی مطالبم شبیه همون چیزی که شما گفتید بود دیگه . value type بودنش را هم در نظر گرفتم و توضیح دادم ها . حالا اگه نیاز شد ، در پست بعد ، با رسم شکل توضیح میدم تا منظورم را بیشتر درک کنید (اگه نیاز شد) .

نه، اطلاعی ندارم. تجربه پرسیدن در مورد نمونه کار و پروژه های قبلی رو داشتم ولی تا حالا ازم آزمون برنامه نویسی نگرفتن. اگر کارشون اصولی باشه ازتون نمیپرسن که فرضا Thread.Sleep چیکار میکنه. صورت مساله ای رو میدن تا به روش خودتون برایش راه حل بدید، یعنی ممکنه سوال یک جواب واحد نداشته باشه و هر کسی به روش خودش حل اش کنه، هدف باید این باشه که ببینند توانایی حل مسائل رو دارید، نه اینکه دستورات #C و کتابخانه های NET. رو میشناسید.

خیلی ممنون استاد .
منظورتون الگوریتم ها که نیست؟ مثل الگوریتم مورچگان و ...؟
حالا نمیدونین چه چیزها و مباحثی را بیشتر تمرکز کنم ، بهتره؟
مثلا کدوم چیزهایی که توی این تاپیک یا تاپیک های دیگه بهم گفتین (یا منابع فارسی و انگلیسی در جاهای دیگه) را پیشنهاد میکنین مرور کنم؟
 
آخرین ویرایش:

the_king

مدیرکل انجمن
خیلی ممنون استاد .
آها پس پروپرتی (وقتی یه مقداری را برمیگردونه و ...) ، دقیق برابر با متد میشه . وقتی توی پروپرتی ، کدی را مینویسیم ، انگار توی متد ، کدی را نوشتیم یا برگردوندیم . بله قبلا گفته بودید .
به value type بودن ، توجه کردم ها . توی توضیح هم گفتم .



الان استاد ، شما میگین ، وقتی کد زیر اجرا میشه (A ، همونطور که کدش را در پست بالا دادم ، یک پروپرتی هست) ، با اونکه پروپرتیِ A مقداردهی میشه (بنابراین مشخص هست که اکسسور set در این پروپرتی اجرا میشه) ، قبل از اکسسور set ، مقدارش فراخونی میشه ؟
یعنی قبل از اینکه این پروپرتی ، مقداردهی بشه (قبل از اینکه اکسسور set اش اجرا بشه) ، مقدارش فراخونی میشه (اکسسور get اش فراخونی میشه) و بعد اکسسور set اش اجرا میشه؟


کد:
this.A = new Point(5, 10);
نه، شما دارید set_A رو اجرا می کنید یک متد void ئه، اصلا کاری هم با get_A نداره. دو تا متد مستقل هستند، دلیلی نداره وقتی set می کنید get اش هم فراخوانی بشه.

اگه منظورتون اینه ، من فکر نکنم این جوری باشه (حتما من منظورتون را بد متوجه شدم). چون موقع مقداردهی ، فقط باید اکسسور set اش اجرا بشه . نیازی نداره که مقدار رو get کنه . مقدارش هر چقدر که بود ، بود . براش قطعا نباید اهمیت داشته باشه چون مقدار جدیدی براش میخواد در نظر گرفته بشه توی break point هم همچین چیزی را نشون نمیده (در کد بالا) .
مثلا در کد زیر :

کد:
this.A.X = 5;

قبول دارم که وقتی تا this.A اجرا شد ، اول میره قسمت get در پروپرتی A را اجرا میکنه تا مقدارش را کپی کنه توی حافظه ی جدیدش تا به عضوِ X در اون استراکچر ، دسترسی پیدا کنه تا بتونه مقدار اون عضو را تغییر بده (هر چند ، در حافظه ی جدید) ولی اون کد خط بالاتر رو قبول ندارم که وقتی شی ای ست میکنیم ، بره اکسسورِ get در پروپرتی را اجرا کنه .
this.A = something شباهتی به this.A.X = something نداره، اولی set_A رو فراخوانی می کنه و دومی get_A رو اجرا می کنه و بعد set_X را روی دریافتی get_A.

خیلی ممنون استاد .
منظورتون الگوریتم ها که نیست؟ مثل الگوریتم مورچگان و ...؟
حالا نمیدونین چه چیزها و مباحثی را بیشتر تمرکز کنم ، بهتره؟
مثلا کدوم چیزهایی که توی این تاپیک یا تاپیک های دیگه بهم گفتین (یا منابع فارسی و انگلیسی در جاهای دیگه) را پیشنهاد میکنین مرور کنم؟
عرض کردم تجربه شو ندارم. برای مسابقات ACM که ازتون آزمون نمیگیرن که الگوریتم های خاص مدنظر شون باشه. اگه کارشون اصولی باشه مسائل عادی و روزمره که لازمشون هست رو میخوان، وگرنه بیشتر این الگوریتم ها برای نرم افزار های تجاری کاربردی نداره، خیلی بندرت استفاده میشن، معمولا بدرد مسائل خاص و مسابقات برنامه نویسی و ... میخوره که از حفظ هم نباشید مهم نیست، موقعی که لازم شد بهشون رجوع میکنید. البته اگه منطقی آزمون بگیرند.
 

SajjadKhati

کاربر فعال <A href="http://forum.majidonline.com/f
نه، شما دارید set_A رو اجرا می کنید یک متد void ئه، اصلا کاری هم با get_A نداره. دو تا متد مستقل هستند، دلیلی نداره وقتی set می کنید get اش هم فراخوانی بشه.


this.A = something شباهتی به this.A.X = something نداره، اولی set_A رو فراخوانی می کنه و دومی get_A رو اجرا می کنه و بعد set_X را روی دریافتی get_A.

خیلی ممنون استاد.
خوب ، منم در پست 1496 ، همین حرف شما را زدم استاد دیگه . شاید حالا کامل توضیح نداده باشم . اما اینکه علاوه بر اینها ، تاکیدم بر این هم بود که وقتی عضوی از اون نوعِ استراکچری که به عنوان پروپرتی در نظر گرفتیم (مثل X در پروپرتی A.X که پروپرتی A از نوع Point هست) ، مقدارش را تغییر بدیم ، اصلا قسمت set در اون پروپرتی اجرا نمیشه که با اجرای اکسسور set در پروپرتی ، حالا مقدار کپی شده ی X را در متغییر مربوط به اون پروپرتی ، بریزه و کپی کنه پس مقدار اون متغییر هم تغییر نمیکنه تا در دفعه ی بعد که پروپرتی مون (موقع خوندن از متغییرِ مربوطه اش) ، مقدار اون متغییر را دوباره توی حافظه ی مربوط به خودش بخواد کپی کنه و اونو بخونه .

عرض کردم تجربه شو ندارم. برای مسابقات ACM که ازتون آزمون نمیگیرن که الگوریتم های خاص مدنظر شون باشه. اگه کارشون اصولی باشه مسائل عادی و روزمره که لازمشون هست رو میخوان، وگرنه بیشتر این الگوریتم ها برای نرم افزار های تجاری کاربردی نداره، خیلی بندرت استفاده میشن، معمولا بدرد مسائل خاص و مسابقات برنامه نویسی و ... میخوره که از حفظ هم نباشید مهم نیست، موقعی که لازم شد بهشون رجوع میکنید. البته اگه منطقی آزمون بگیرند.

استاد ، الان ، علاوه بر مفاهیم سی شارپ ، شما میگین روی الگوریتم ها مثل جستجوی خطی و دودویی و درختی و اینها هم تمرین و تمرکز کنم؟
متوجه شدم تجربه ندارین . ولی حدس تون ، قطعا بهتر از من خواهد بود . مثلا الان روی این الگوریتم هایی که گفتم هم تمرکز کنم؟ الگوریتم یا مبحث دیگه ای مد نظرتون هست؟
 

جدیدترین ارسال ها

بالا