پاسخ به بازخورد‌های پروژه‌ها
نمایش متن در زیر جدول مربوط به هر گروه
فقط مورد اول رو خودتون فعلا دستی باید محاسبه کنید. اگر شماره گروه صفر بود، اضافه نشود (این یک مورد باید در سورس کتابخانه اصلاح شود). این شماره گروه هم یک متغیر ساده باشد که با هربار فراخوانی GroupAdded یک شماره اضافه می‌شود.
به صورت خلاصه از اولین callback آن فعلا صرفنظر کنید. مابقی درست است.
پاسخ به بازخورد‌های پروژه‌ها
استفاده از سطح دوم کش در EF
بله. روش AOP دقیق‌تر هست و سربار کمتری هم داره، چون سطح دوم کشی که بر اساس محاسبه هش expression تولیدی کار می‌کنه این مرحله خاص رو اضافه‌تر داره به علاوه اینکه حالت many-to-many رو هم درست مدیریت نمی‌کنه و نیاز به یک سری تمهیدات جانبی مثل اضافه‌کردن پارامترهای ورودی در آن هست.
نظرات مطالب
Implementing second level caching in EF code first
سلام و خسته نباشید
به این نتیجه رسیدم که اگر متد ToCacheableList()  را در انتها اضافه نکنیم چیزی شبیه به این
var list2 = context.Products
        .OrderBy(one => one.ProductNumber)
        .Where(x => x.IsActive == isActive && x.ProductName == name);
روی کش هیچ تاثیری نداره یعنی نه چیزی رو کش می‌کنه و نه چیزی رو از کش می‌خونه و نه کش رو پاک می‌کنه ولی list3 دوباره اطلاعات رو از کش می‌خونه آیا این موضوع صحیح است ؟
با تشکر 
مطالب
نکاتی توصیه ای برای برنامه نویسی اندروید : قسمت اول
اگر جدیدا قصد برنامه نویسی اندروید را کرده‌اید، یا هنوز روش‌های متدوالی را برای کار با این زبان انتخاب نکرده‌اید؛ به نظرم این مقاله می‌تواند کمک خوبی برای شما باشد. مسائلی که بیان میکنم در واقع از تجربیات شخصی و راه حل هایی است که برای خودم تعیین کرده‌ام و تعدادی از آن‌ها را در طول مدتی که در این زمینه فعالیت کرده‌ام، از جاهای مختلف دیده و در یک جا گردآوری کرده‌ام. برای نامگذاری اشیاء و متغیرها و دیگر موارد، من از این قاعده پیروی میکنم که به نظرم بسیار ایده آل می‌باشد. الگوی معماری هم که جدیدا مورد استفاده قرار داده‌ام، الگوی MVP است که نمونه‌ای از آن، در گیت هاب قرار گرفته است. البته این مثال ساده تر نیز وجود دارد. تشریح کامل این معماری را به همراه آزمون واحد آن، می‌توانید در این مقاله سه قسمتی ببینید.

در اینجا، یک سری نکات را در طول برنامه نویسی، متذکر می‌شوم تا مدیریت کدهای شما را در اندروید راحت‌تر کند.

یک نکته‌ی دیگر را که باید متذکر شوم این است که همه اصطلاحاتی که در این مقاله استفاده می‌شوند بر اساس اندروید استادیو و مستندات رسمی گوگل است است؛ به عنوان نمونه عبارت‌های ماژول و پروژه آن چیزی هستند که ما در اندروید استادیو به آن‌ها اشاره می‌کنیم، نه آنچه که کاربران Eclipse به آن اشاره می‌کنند.

یک. برای هر تکه کد و یا متدی که می‌نویسید مستندات کافی قرار دهید و اگر این متد نیاز به مجوز خاصی دارد مانند نمونه زیر، آن را حتما ذکر کنید:
/**
     *
     * <p>
     *   check network is available or not <br/>
     *   internet connection is not matter,for check internet connection refer to IsInternetConnected() Method in this class
     * </p>
       * <p>
     *   Required Permission : <b>android.permission.ACCESS_NETWORK_STATE</b>
     * </p>
     * @param context
     * @return returns true if a network is available
     */
    public boolean isNetworkAvailable(Context context) {

        ConnectivityManager connectivityManager
                = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
        return activeNetworkInfo != null && activeNetworkInfo.isConnected();
    }
همچنین اگر، مورد خاص دیگری مثل بالا بود، حتما آن را ذکر کنید. می‌توانید از تگ گذاری در کامنت ها نیز استفاده کنید. از ویژگی‌های کامنت todo در اندروید استادیو این است که میتوانید در حین کار با سیستم گیت نیز از آن بهره ببرید  و قبل از کامیت کردن کد، کدهای todo به شما یادآوری شوند و هر پیکربندی را که لازم دارید، روی آن انجام دهید.



دو. از یک کلاس واحد جهت استفاده از اطلاعات عمومی و یا ثابت‌ها استفاده نمایید. این اطلاعات می‌توانند شامل: مسیرها، آدرس‌های وب سرویس، شماره اختصاصی هر نوتیفیکیشن و .... باشند. برای اینکار میتوان هر کدام از اطلاعات را داخل یک کلاس قرار داد و همه این کلاس‌ها را به صورت استاتیک تعریف کنید تا بدین شکل در دسترس قرار بگیرند (از الگوی singleton هم می‌توان استفاده کرد).
public class ProjectSettings
{
       public static NotificationsId=new NotificationsId();
       public static UrlAddresss=new UrlAddresss();
       public static SdPath=new SdPath();
       ......
}
نحوه صدا زدن هم به همین شکل می‌شود:
ProjectSettings.NotificationsId.UpdateNotificationId
بدین شکل هم به طور ساده و مفهومی صدا زده می‌شود و هم اینکه در همه جای برنامه این ثابت‌ها و مقادیر قابل استفاده هستند. به عنوان مثال به شماره هر نوتیفیکیشن از همه جا دسترسی دارید و هم اینکه شماره‌ای تکراری اشتباها انتخاب نمی‌شود.

سه. حداکثر استفاده از اینترفیس را به خصوص برای UI انجام بدهید:
به عنوان نمونه، بسیاری نمایش یک toast را به شکل زیر انجام می‌دهند:
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
یا اینکه برای یک دیالوگ مستقیما و در جا همانجا به کدنویسی مشغول می‌شوند. این روش‌ها هیچ مشکلی ندارند ولی در آینده نگهداری کد را مشکل می‌کنند. مثلا تصور کنید شما بسیاری از جاهای برنامه، Toast زدید و حالا قصد دارید در نسخه بعدی برنامه، toast‌های دلخواه و یا custom ایی را ایجاد کنید. در این صورت مجبورید کل برنامه را رصد کرده و هر جا toast هست آن را تغییر دهید. در اینجا هم اصول DRY را نادیده گرفته‌اید و هم زحمت شما زیاد شده‌است و حتی ممکن است یک یا چندتایی از قلم بیفتند. برای دیالوگ‌ها هم بدین صورت خواهد بود و خیلی از مسائل دیگر. به همین جهت استفاده از اینترفیس‌ها توصیه می‌شود و فردا نیز اگر باز یک کلاس دیگر را نوشتید، خیلی راحت آن را با کلاس فعلی تعویض می‌کنید.
public interface IMessageUI
{
    void ShowToast(Context context,String message);
}

public class MessageUI impelement IMessageUI
{
      public void ShowToast(Context context,string message)
       {
              Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
       }
}

چهار
. اگر برای اولین بار است وارد اندروید می‌شوید، خوب چرخه‌های یک شیء، چون اکتیویتی یا فراگمنت را یاد بگیرید تا در آینده با مشکلات خاصی روبرو نشوید.
به عنوان مثال درست است که اولین رویداد فراخوانی در onCreate رخ میدهد ولی همیشه محل مناسبی برای دریافت دیتاها در زمان اولیه نیست. به عنوان مثال تصور کنید که لیستی در اکتیویتی A دارید و به اکتیویتی B می‌روید و یک آیتم به اطلاعات اضافه می‌شود و موقعی که به اکتیویتی A بر می‌گردید، زیاد تعجب نکنید که لیست دقیقا به همان شکل قبلی است و خبری از آیتم جدید نیست.
  چون اکتیویتی در حالت stop بوده و بعد از آن به حالت Resume رفته و تا موقعی که این اکتیویتی از حافظه خارج نشود یا گوشی چرخش نداشته باشد، واکشی دیتاها صورت نخواهد گرفت. پس بهترین مکان در این حالت، رویداد OnStart است که در هر دو وضعیت صدا زده می‌شود؛ یا اینکه در OnRestatr روی آداپتور تغییرات جدید را اعمال کنید تا نیازی به واکشی مجدد داده‌ها نباشد. 



به طور خلاصه نحوه اجرای رویدادها بدین شکل است که ابتدای رویداد OnCreate اجرا می‌شود که هنوز هیچ UI ئی در آن پیاده سازی نشده‌است و شما در اینجا موظفید Layout خود را معرفی کنید. رویداد OnStart بعد از آن موقعی که UI آماده شده است، اجرا می‌گردد. سپس رویداد OnResume اجرا می‌شود.

 تا بدینجا اکتیویتی مشکلی ندارد و میتواند به عملیات پاسخ دهد ولی اگر قسمتی از اکتیویتی در زیر لایه‌ای از UI پنهان شود، به عنوان مثال دیالوگی باز شود که قسمتی از اکتیویتی را بپوشاند و یا منویی همانند تلگرام قسمتی از صفحه را بپوشاند، اکتیویتی اصطلاحا در حالت Pause قرار گرفته و بدین ترتیب رویداد OnPause اجرا می‌گردد. اگر همین دیالوگ بسته شود و مجددا اکتیویتی به طور کامل نمایان گردد مجددا رویداد OnResume اجرا می‌گردد.

از رویداد Onresume میتوانید برای کارهایی که بین زمان آغاز اکتیویتی و برگشت اکتیویتی مشترکند استفاده کرد. اگر به هر نحوی اکتیویتی به طور کامل پنهان شود٬، به این معناست که شما به اکتیویتی دیگری رفته‌اید رویداد OnStop اجرا شده‌است و در صورت بازگشت، رویداد OnRestart اجرا خواهد شد. ولی اگر مدت طولانی از رویداد OnStop بگذرد احتمال اینکه سیستم مدیریت منابع اندروید، اکتیویتی شما را از حافظه خارج کند زیاد است و رویداد OnDestroy صورت خواهد گرفت. در این حالت دفعه بعد، مجددا همه عملیات از ابتدا آغاز می‌گردند.

پنج. سرویس را با تردهای UI ترکیب نکنید. بعضا دیده می‌شود که کاربران AsyncTask را داخل سرویس استفاده می‌کنند ولی این را بدانید که سرویس یک ترد پردازشی جداگانه است و تضمینی برای ارتباط با UI به شما نمی‌دهند. هر چند گوگل جدیدا تمهیداتی را برای آن اندیشیده است که به شما اجازه اینکار را نمی‌دهد. ولی اگر باز هم اندروید استادیو به شما خورده‌ای نگرفت، خودتان این قانون را اجرا کنید. قرار نیست یک AsyncTask با سرویس ترکیب شود.

شش. اگر برنامه شما قرار است در چندین حالت مختلفی که اتفاق می‌افتد، یک کار خاصی را انجام دهد، برای برنامه‌تان یک Receiver بنویسید و در آن کدهای تکراری را نوشته و در محل‌های مختلف وقوع آن رویدادها، رسیور را صدا بزنید. برای نمونه برنامه تلگرام یک سرویس پیام رسان پشت صحنه دارد که در دو رویداد قرار است اجرا شوند. یکی موقعی که گوشی بوت خود را تکمیل کرده است و در حال آغاز فرایندهای سیستم عامل است و دیگر زمانی است که برنامه اجرا می‌شود. در اینجا تلگرام از یک رسیور سیستمی برای آگاهی از بوت شدن و یک رسیور داخل برنامه جهت آگاهی از اجرای برنامه استفاده میکند و هر دو به یک کلاس از جنس BroadcastReceiver متصلند:
      <receiver android:name=".AppStartReceiver" android:enabled="true">
            <intent-filter>
                <action android:name="org.telegram.start" />
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

public class AppStartReceiver extends BroadcastReceiver {
    public void onReceive(Context context, Intent intent) {
        AndroidUtilities.runOnUIThread(new Runnable() {
            @Override
            public void run() {
                ApplicationLoader.startPushService();
            }
        });
    }
}
برای نام رسیورهای داخلی هم میتوانید مورد شماره 2 را اجرا کنید.
برنامه تلگرام حتی برای حالت‌های پخش هم رسیورها استفاده کرده است که در همین رسیور وضعیت تغییر پلیر مشخص می‌شود:
    <receiver android:name=".MusicPlayerReceiver" >
            <intent-filter>
                <action android:name="org.telegram.android.musicplayer.close" />
                <action android:name="org.telegram.android.musicplayer.pause" />
                <action android:name="org.telegram.android.musicplayer.next" />
                <action android:name="org.telegram.android.musicplayer.play" />
                <action android:name="org.telegram.android.musicplayer.previous" />
                <action android:name="android.intent.action.MEDIA_BUTTON" />
                <action android:name="android.media.AUDIO_BECOMING_NOISY" />
            </intent-filter>
        </receiver>
اینگونه تلگرام میتواند از همه جا سرویس را کنترل کند. مثلا موقعی که دانلود یک موزیک تمام شده، سریعا پخش آن موزیک دانلود شده را آغاز کند.

هفت. اگر از یک ORM برای لایه داده‌ها استفاده می‌کنید (قبلا در سایت جاری در مورد ORM‌های اندروید صحبت کرده‌ایم و  ORM‌های خوش دستی که خودم از آن‌ها استفاده میکنم ActiveAndroid و CPORM هستند که هم کار کردن با آنها راحت است و هم اینکه امکانات خوبی را عرضه می‌کنند) در این نوع ORM‌ها شما نباید انتظار چیزی مانند EF را داشته باشید و در بعضی موارد باید کمی خودتان کمک کنید. به عنوان مثال در Active Android برای ایجاد یک inner join باید به شکل زیر بنویسید:
     From query= new Select()
               .from(Poem.class)
               .innerJoin(BankPoemsGroups.class)
               .on("poems.id=bank_poems_groups.poem")
               .where("BankGroup=?", String.valueOf(groupId));
        return query.execute();
همانطور که می‌بینید بخش‌هایی از آن مثل جوین‌ها و شرط‌ها را باید خودتان تکمیل کنید. از آنجا که ممکن است در آینده نام فیلد تغییر کند یا اینکه در حین انبوهی از کدها، عبارت رشته‌ای را اشتباه وارد کنید، بهتر است به این فرم کار کنید:
@Table(name="poems")
public class Poem extends Model {

    public static String tableName="poems";
    public static  String codeColumn="code";
    public static  String titleColumn="title";
    public static  String bookColumn="book";
    ......

    @Column(name="code",index = true)
    public int Code;

    @Column(name="title")
    public String Title;

    @Column(name="book")
    public Book Book;
.....}
در مدل بالا، نام فیلدها و جداول به صورت استاتیک تعریف شده‌اند. حالا می‌توانیم از این اسامی به راحتی در لایه سرویس استفاده کنیم:
    From query= new Select()
               .from(Poem.class)
               .innerJoin(BankPoemsGroups.class)
               .on(Poem.TableName+"."+ Poem.IdColumn+"="+ BankPoemsGroups.TableName+"."+ BankPoemsGroups.PoemColumn)
               .where(Poem.BankGroupColumn+"=?", String.valueOf(groupId));
        return query.execute();
حالا کمی بهتر شد. هم برای تغییر آینده بهتر شد و هم اینکه احتمال خطای تایپی کاهش یافت. ولی باز هم ایجاد کوئری هنوز سخت است و نوشتن مرتب یک رابطه جوین و شرطی و چسباندن مداوم رشته‌ها کار خسته کننده‌ای است و احتمال خطای سهوی و انسانی هم در آن بالاست. برای رفع این مشکل بهتر است یک کلاس جدید برای ساخت این کوئری‌ها داشته باشیم که یک نمونه از آن را در این پایین می‌بینید:
public class QueryConcater {
  public String GetInnerJoinQuery(String table1,String field1,String table2,String field2)
    {
        String query=table1 +"." +field1+"="+table2+"."+field2;
        return query;
    }
......
}
در ادامه برای مرتب سازی و شرط و ... هم می‌نویسیم:
   return new Select()
                .from(Color.class)
                .innerJoin(ProductItem.class)
                .on(queryConcater.GetInnerJoinQuery(ProductItem.TableName,
                        ProductItem.ColorColumn, Color.TableName))
                .where(queryConcater.WhereConditionQuery
                        (ProductItem.TableName, ProductItem.ProductColumn), productId)
                .execute();
در دستورات بالا از این کلاس دو متد برای کوئری جوین و یکی هم برای ساخت شرط ایجاد شده است و مقادیر به صورت پارامتر داده شده‌اند. این الگو کمک میکند که اگر هم این تکه کد اشتباه باشد، با تغییر یکجا بقیه کدها هم تغییر میکنند و اگر در آینده هم ORM تغییر یافت، نحوه کوئری نویسی‌ها در این کلاس تغییر کنند، نه اینکه در طول لایه سرویس پراکنده باشند.

هشت. سعی کنید همیشه از یک سیستم گزارش خطا در اپلیکیشن خود استفاده کنید. در حال حاضر معروفترین سیستم گزارش خطا Acra است که می‌توانید backend آن را هم از اینجا تهیه کنید و اگر هم نخواستید، سایت Tracepot امکانات خوبی را به رایگان برای شما فراهم می‌کند. از این پس با سیستم آکرا شما به یک سیستم گزارش خطا متصلید که خطاهای برنامه شما در گوشی کاربر به شما گزارش داده خواهد شد. این گزارش‌ها شامل:
  • وضعیت گوشی در حین باز شدن برنامه و در حین خطا چگونه بوده است.
  • مشخصات گوشی
  • این خطا به چه تعداد رخ داده است و برای چه تعداد کاربر
  • گزارش گیری بر اساس اولین تاریخ رخداد خطا و آخرین تاریخ، نسخه سیستم عامل اندروید، ورژن برنامه شما و...
و امکانات دیگر.

نه. آکرا همانند Elmah نمی‌تواند خطاهای catch شده را دریافت کند. برای حل این مشکل عبارت زیر را در catch‌ها بنویسید:
ACRA.getErrorReporter().handleException(caughtException);

ده. بر خلاف سیستم دات نت که شما اجباری به استفاده از Try Catch‌ها ندارید. در جاوا اینگونه نیست و هر متدی که Throw روی آن انجام شده باشد مستلزم  استفاده از catch است. به همین دلیل در شماره نه گفتیم که چگونه باید این مشکل را حل کنیم. ولی در بسیاری از اوقات پیش می‌آید که ما داریم از ماژول‌های متفاوتی استفاده میکنیم که جدا از ماژول اصلی برنامه هستند و این مورد باعث می‌شود که بعضی افراد یا Acra را در همه ماژول‌ها صدا بزنند یا اینکه بی خیال آن شوند. ولی کار راحت‌تر این است که شما هم همانند برنامه نویسان جاوا متد خود را به Throw مزین کنید تا در هنگام استفاده از آن در برنامه اصلی نیاز به catch شدن باشد. در واقع شما نباید catch‌ها را داخل یک کتابخانه جدا و مستقل قرار دهید و روش صحیح هم همین است حالا چه استفاده از آکرا نیاز باشد و چه نباشد.

نمونه اشتباه:
 public  void CopyFile(String source,String destination,CopyFileListener copyFileListener) {
        try {
        InputStream in = new FileInputStream(source);
        OutputStream out = new FileOutputStream(destination);

        long fileLength=new File(source).length();
        // Transfer bytes from in to out
        byte[] buf = new byte[64*1024];
        int len;
        long total=0;
        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
            total+=len;
            copyFileListener.PublishProgress(fileLength,total);
        }
        in.close();

            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
نمونه صحیح:
    public  void CopyFile(String source,String destination,CopyFileListener copyFileListener) throws IOException {

        InputStream in = new FileInputStream(source);
        OutputStream out = new FileOutputStream(destination);

        long fileLength=new File(source).length();
        // Transfer bytes from in to out
        byte[] buf = new byte[64*1024];
        int len;
        long total=0;
        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
            total+=len;
            copyFileListener.PublishProgress(fileLength,total);
        }
        in.close();

            out.close();
    }
  ادامه دارد...
مطالب
بررسی روش آپلود فایل‌ها در ASP.NET Core
مدیریت پردازش آپلود فایل‌ها در ASP.NET Core نسبت به ASP.NET MVC 5.x به طور کامل تغییر کرده‌است و اینبار بجای ذکر نوع System.Web.HttpPostedFileBase باید از اینترفیس جدید IFormFile واقع در فضای نام Microsoft.AspNetCore.Http کمک گرفت.


مراحل فعال سازی آپلود فایل‌ها در ASP.NET Core

مرحله‌ی اول فعال سازی آپلود فایل‌ها در ASP.NET Core، شامل افزودن ویژگی "enctype="multipart/form-data به یک فرم تعریف شده‌است:
<form method="post"
      asp-action="Index"
      asp-controller="TestFileUpload"
      enctype="multipart/form-data">
    <input type="file" name="files" multiple />
    <input type="submit" value="Upload" />
</form>
در اینجا همچنین ذکر ویژگی multiple در input از نوع file، امکان ارسال چندین فایل با هم را نیز میسر می‌کند.
در سمت سرور، امضای اکشن متد دریافت کننده‌ی این فایل‌ها به صورت ذیل خواهد بود:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Index(IList<IFormFile> files)
در اینجا نام پارامتر تعریف شده، باید دقیقا مساوی نام input از نوع file باشد. همچنین از آنجائیکه ویژگی multiple را نیز در سمت کلاینت قید کرده‌ایم، این پارامتر سمت سرور از نوع یک لیست، تعریف شده‌است. اگر ویژگی multiple را حذف کنیم می‌توان آن‌را به صورت ساده‌ی IFormFile files نیز تعریف کرد.


یافتن جایگزینی برای Server.MapPath در ASP.NET Core

زمانیکه فایل ارسالی، در سمت سرور دریافت شد، مرحله‌ی بعد، ذخیره سازی آن بر روی سرور است و از آنجائیکه ما دقیقا نمی‌دانیم ریشه‌ی سایت در کدام پوشه‌ی سرور واقع شده‌است، می‌شد از متد Server.MapPath برای یافتن دقیق آن کمک گرفت. با حذف این متد در ASP.NET Core، روش یافتن ریشه‌ی سایت یا همان پوشه‌ی wwwroot در اینجا شامل مراحل ذیل است:
public class TestFileUploadController : Controller
{
    private readonly IHostingEnvironment _environment;
    public TestFileUploadController(IHostingEnvironment environment)
    {
        _environment = environment;
    }
ابتدا اینترفیس توکار IHostingEnvironment را در سازنده‌ی کلاس تزریق می‌کنیم. سرویس HostingEnvironment جزو سرویس‌های از پیش تعریف شده‌ی ASP.NET Core است و نیازی به تنظیمات اضافه‌تری ندارد. همینقدر که ذکر شود، به صورت خودکار توسط ASP.NET Core مقدار دهی و تامین می‌گردد.
پس از آن خاصیت environment.WebRootPath_ به ریشه‌ی پوشه‌ی wwwroot برنامه، بر روی سرور اشاره می‌کند. به این ترتیب می‌توان مسیر دقیقی را جهت ذخیره سازی فایل‌های رسیده، مشخص کرد.


امکان ذخیره سازی async فایل‌ها در ASP.NET Core

عملیات کار با فایل‌ها، عملیاتی است که از مرزهای IO سیستم عبور می‌کند. به همین جهت یکی از بهترین مثال‌های پیاده سازی async، جهت رها سازی تردهای برنامه و بالا بردن میزان پاسخ‌دهی آن با بالا بردن تعداد تردهای آزاد بیشتر است. در ASP.NET Core، نوشتن async محتوای فایل رسیده در یک stream پشتیبانی می‌شود و این stream می‌تواند یک FileStream و یا MemoryStream باشد. در ذیل نحوه‌ی کار async با یک FileStream را مشاهده می‌کنید:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Index(IList<IFormFile> files)
{
    var uploadsRootFolder = Path.Combine(_environment.WebRootPath, "uploads");
    if (!Directory.Exists(uploadsRootFolder))
    {
        Directory.CreateDirectory(uploadsRootFolder);
    }
 
    foreach (var file in files)
    {
        if (file == null || file.Length == 0)
        {
            continue;
        }
 
        var filePath = Path.Combine(uploadsRootFolder, file.FileName);
        using (var fileStream = new FileStream(filePath, FileMode.Create))
        {
            await file.CopyToAsync(fileStream).ConfigureAwait(false);
        }
    }
    return View();
}
در اینجا کدهای کامل متد دریافت فایل‌ها را در سمت سرور مشاهده می‌کنید. ابتدا با استفاده از خاصیت environment.WebRootPath_، به مسیر ریشه‌ی wwwroot دسترسی و سپس پوشه‌ی uploads را در آن جهت ذخیره سازی فایل‌های دریافتی، تعیین کرده‌ایم.
چون برنامه‌های ASP.NET Core قابلیت اجرای بر روی لینوکس را نیز دارند، تا حد امکان باید از Path.Combine جهت جمع زدن اجزای مختلف یک میسر، استفاده کرد. از این جهت که در لینوکس، جداکننده‌ی اجزای مسیرها، / است بجای \ در ویندوز و متد Path.Combine به صورت خودکار این مسایل را لحاظ خواهد کرد.
در آخر با استفاده از متد file.CopyToAsync کار نوشتن غیرهمزمان محتوای فایل دریافتی در یک FileStream انجام می‌شود؛ به همین جهت در امضای متد فوق، <async Task<IActionResult را نیز ملاحظه می‌کنید.


پشتیبانی کامل از Model Binding آپلود فایل‌ها در ASP.NET Core

در ASP.NET MVC 5.x اگر ویژگی Required را بر روی یک خاصیت از نوع HttpPostedFileBase قرار دهید ... کار نمی‌کند و در سمت کلاینت تاثیری را به همراه نخواهد داشت؛ مگر اینکه تنظیمات سمت کلاینت آن‌را به صورت دستی انجام دهیم. این مشکلات در ASP.NET Core، کاملا برطرف شده‌اند:
public class UserViewModel
{
    [Required(ErrorMessage = "Please select a file.")]
    [DataType(DataType.Upload)]
    public IFormFile Photo { get; set; }
}
در اینجا یک خاصیت از نوع IFormFile، با دو ویژگی Required و DataType خاص آن در یک ViewModel تعریف شده‌اند. فرم معادل آن در ASP.NET Core به صورت ذیل خواهد بود:
@model UserViewModel
 
<form method="post"
      asp-action="UploadPhoto"
      asp-controller="TestFileUpload"
      enctype="multipart/form-data">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
 
    <input asp-for="Photo" />
    <span asp-validation-for="Photo" class="text-danger"></span>
    <input type="submit" value="Upload"/>
</form>
در اینجا ابتدا نوع مدل View تعیین شده‌است و سپس با استفاده از Tag Helpers، صرفا یک input را به خاصیت Photo مدل View جاری متصل کرده‌ایم. همین اتصال سبب فعال سازی مباحث اعتبارسنجی سمت سرور و کاربر نیز می‌شود.
اینبار جهت فعال سازی و استفاده‌ی از قابلیت‌های Model Binding می‌توان از ModelState نیز بهره گرفت:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhoto(UserViewModel userViewModel)
{
    if (ModelState.IsValid)
    {
        var formFile = userViewModel.Photo;
        if (formFile == null || formFile.Length == 0)
        {
            ModelState.AddModelError("", "Uploaded file is empty or null.");
            return View(viewName: "Index");
        }
 
        var uploadsRootFolder = Path.Combine(_environment.WebRootPath, "uploads");
        if (!Directory.Exists(uploadsRootFolder))
        {
            Directory.CreateDirectory(uploadsRootFolder);
        }
 
        var filePath = Path.Combine(uploadsRootFolder, formFile.FileName);
        using (var fileStream = new FileStream(filePath, FileMode.Create))
        {
            await formFile.CopyToAsync(fileStream).ConfigureAwait(false);
        }
 
        RedirectToAction("Index");
    }
    return View(viewName: "Index");
}
اگر ModelState معتبر باشد، کار ذخیره سازی تک فایل رسیده را انجام می‌دهیم. سایر نکات این متد، با اکشن متد Index که پیشتر بررسی شد، یکی هستند.


بررسی پسوند فایل‌های رسیده‌ی به سرور

ASP.NET Core دارای ویژگی است به نام FileExtensions که ... هیچ ارتباطی به خاصیت‌هایی از نوع IFormFile ندارد:
 [FileExtensions(Extensions = ".png,.jpg,.jpeg,.gif", ErrorMessage = "Please upload an image file.")]
ویژگی FileExtensions صرفا جهت درج بر روی خواصی از نوع string طراحی شده‌است. بنابراین قرار دادن این ویژگی بر روی خاصیت‌هایی از نوع IFormFile، سبب فعال سازی اعتبارسنجی سمت سرور پسوندهای فایل‌های رسیده، نخواهد شد.
در ادامه جهت بررسی پسوندهای فایل‌های رسیده، می‌توان یک ویژگی اعتبارسنجی سمت سرور جدید را طراحی کرد:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class UploadFileExtensionsAttribute : ValidationAttribute
{
    private readonly IList<string> _allowedExtensions;
    public UploadFileExtensionsAttribute(string fileExtensions)
    {
        _allowedExtensions = fileExtensions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
    }
 
    public override bool IsValid(object value)
    {
        var file = value as IFormFile;
        if (file != null)
        {
            return isValidFile(file);
        }
 
        var files = value as IList<IFormFile>;
        if (files == null)
        {
            return false;
        }
 
        foreach (var postedFile in files)
        {
            if (!isValidFile(postedFile)) return false;
        }
 
        return true;
    }
 
    private bool isValidFile(IFormFile file)
    {
        if (file == null || file.Length == 0)
        {
            return false;
        }
 
        var fileExtension = Path.GetExtension(file.FileName);
        return !string.IsNullOrWhiteSpace(fileExtension) &&
               _allowedExtensions.Any(ext => fileExtension.Equals(ext, StringComparison.OrdinalIgnoreCase));
    }
}
در اینجا با ارث بری از کلاس پایه ValidationAttribute و بازنویسی متد IsValid آن، کار اعتبارسنجی پسوند فایل‌ها و یا فایل رسیده را انجام داده‌ایم. این ویژگی جدید اگر بر روی خاصیتی از نوع IFormFile قرار بگیرد، پارامتر object value متد IsValid آن حاوی اطلاعات فایل و یا فایل‌های رسیده، خواهد بود. بر این اساس می‌توان تصمیم گیری کرد که آیا پسوند این فایل، مجاز است یا خیر.
public class UserViewModel
{
    [Required(ErrorMessage = "Please select a file.")]
    //`FileExtensions` needs to be applied to a string property. It doesn't work on IFormFile properties, and definitely not on IEnumerable<IFormFile> properties.
    //[FileExtensions(Extensions = ".png,.jpg,.jpeg,.gif", ErrorMessage = "Please upload an image file.")]
    [UploadFileExtensions(".png,.jpg,.jpeg,.gif", ErrorMessage = "Please upload an image file.")]
    [DataType(DataType.Upload)]
    public IFormFile Photo { get; set; }
}
در اینجا روش استفاده‌ی از این ویژگی اعتبارسنجی جدید را نیز با تکمیل ViewModel کاربر، مشاهده می‌کنید. پس از آن تنها بررسی if (ModelState.IsValid) در یک اکشن متد، نتیجه‌ی دریافتی از اعتبارسنج جدید UploadFileExtensions را در اختیار ما قرار می‌دهد و بر این اساس می‌توان تصمیم‌گیری کرد که آیا باید فایل رسیده را ذخیره کرد یا خیر.
مطالب
بارگذاری UserControl در WPF به کمک الگوی MVVM
در نرم افزارهای تحت ویندوز روشها و سلیقه‌های متفاوتی برای چینش فرمها ، منو‌ها و دیگر اجزای برنامه وجود دارد. در یک نرم افزار اتوماسیون اداری که فرمهای ورود اطلاعات زیادی دارد فضای کافی برای نمایش همه‌ی فرم‌ها به کاربر نیست. یکی از روش هایی که می‌تواند به کار رود تقسیم قسمت‌های مختلف نرم افزار در View‌های جداگانه است. این کار استفاده‌ی مجدد از قسمت‌های مختلف و نگهداری کد را سهولت می‌بخشد. 

الگوی متداولی که در نرم افزار‌های WPF و Silverlight استفاده می‌شود الگوی MVVM است. (این الگو در جاوااسکریپت هم به سبب Statefull بودن استفاده می‌شود.) قبلا مطالب زیادی در این سایت جهت آموزش و توضیح این الگوی منتشر شده است.
فرض کنید نرم افزار از چند بخش تشکیل شده :

  • صفحه‌ی اصلی
  • منو
  • یک صفحه‌ی خوش آمدگویی
  • صفحه‌ی ورود و نمایش اطلاعات
می توان اجزا و تعریف هر یک از این قسمت‌ها را در یک UserControl قرار داد و در زمان مناسب آن را بارگذاری کرد. 
سوالی که مطرح است بارگذاری UserControl‌ها به کمک الگوی MVVM چگونه است ؟ 
کدهای XAML صفحه‌ی اصلی : 
<Window x:Class="TwoViews.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MVVM Light View Switching"
        d:DesignHeight="300"
        d:DesignWidth="300"
        DataContext="{Binding Main,
                              Source={StaticResource Locator}}"
        ResizeMode="NoResize"
        SizeToContent="WidthAndHeight"
        mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <ContentControl Content="{Binding CurrentViewModel}" />

        <DockPanel Grid.Row="1" Margin="5">
            <Button Width="75"
                    Height="23"
                    Command="{Binding SecondViewCommand}"
                    Content="Second View"
                    DockPanel.Dock="Right" />
            <Button Width="75"
                    Height="23"
                    Command="{Binding FirstViewCommand}"
                    Content="First View"
                    DockPanel.Dock="Left" />
        </DockPanel>
    </Grid>
</Window>

2 دکمه در صفحه‌ی اصلی وجود دارد ، یکی از آنها وظیفه‌ی بارگذاری View اول و دیگری وظیفه‌ی بارگذاری View دوم را دارد ،  این دکمه‌ها نقش منو را در یک نرم افزار واقعی به عهده دارند. 
کدهای View-Model گره خورده (به کمک الگوی ViewModolLocator ) به View اصلی  : 
    /// This is our MainViewModel that is tied to the MainWindow via the 
    /// ViewModelLocator class.
    /// </summary>
    public class MainViewModel : ViewModelBase
    {
        /// <summary>
        /// Static instance of one of the ViewModels.
        /// </summary>
        private static readonly SecondViewModel SecondViewModel = new SecondViewModel();
        /// <summary>
        /// Static instance of one of the ViewModels.
        /// </summary>
        private static readonly FirstViewModel FirstViewModel = new FirstViewModel();
        /// <summary>
        /// The current view.
        /// </summary>
        private ViewModelBase _currentViewModel;
        /// <summary>
        /// Default constructor.  We set the initial view-model to 'FirstViewModel'.
        /// We also associate the commands with their execution actions.
        /// </summary>
        public MainViewModel()
        {
            CurrentViewModel = FirstViewModel;
            FirstViewCommand = new RelayCommand(ExecuteFirstViewCommand);
            SecondViewCommand = new RelayCommand(ExecuteSecondViewCommand);
        }
        /// <summary>
        /// The CurrentView property.  The setter is private since only this 
        /// class can change the view via a command.  If the View is changed,
        /// we need to raise a property changed event (via INPC).
        /// </summary>
        public ViewModelBase CurrentViewModel
        {
            get { return _currentViewModel; }
            set
            {
                if (_currentViewModel == value)
                    return;
                _currentViewModel = value;
                RaisePropertyChanged("CurrentViewModel");
            }
        }
        /// <summary>
        /// Simple property to hold the 'FirstViewCommand' - when executed
        /// it will change the current view to the 'FirstView'
        /// </summary>
        public ICommand FirstViewCommand { get; private set; }
        /// <summary>
        /// Simple property to hold the 'SecondViewCommand' - when executed
        /// it will change the current view to the 'SecondView'
        /// </summary>
        public ICommand SecondViewCommand { get; private set; }
        /// <summary>
        /// Set the CurrentViewModel to 'FirstViewModel'
        /// </summary>
        private void ExecuteFirstViewCommand()
        {
            CurrentViewModel = FirstViewModel;
        }
        /// <summary>
        /// Set the CurrentViewModel to 'SecondViewModel'
        /// </summary>
        private void ExecuteSecondViewCommand()
        {
            CurrentViewModel = SecondViewModel;
        }
    }

این ViewModel از کلاس پایه‌ی چارچوب MVVM Light مشتق شده است.  Command‌ها جهت Handle کردن کلیک دکمه‌ها هستند . نکته‌ی اصلی این ViewModel پراپرتی CurrentViewModel می‌باشد.  این پراپرتی به ویژگی Content کنترل ContentControl مقید (Bind) شده است. با کلیک شدن روی دکمه‌ها View مورد نظر به کاربر نمایش داده می‌شود.
WPF از کجا می‌داند کدام View را به ازای ViewModel خاص render کند ؟ 
در فایل App.xaml یک سری DataTemplate تعریف شده است : 
    <Application.Resources>
        <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
        <!--
            We define the data templates here so we can apply them across the
            entire application.
         
            The data template just says that if our data type is of a particular
            view-model type, then render the appropriate view.  The framework
            takes care of this dynamically.  Note that the DataContext for
            the underlying view is already set at this point, so the
            view (UserControl), doesn't need to have it's DataContext set
            directly.
        -->
        <DataTemplate DataType="{x:Type vm:SecondViewModel}">
            <views:SecondView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type vm:FirstViewModel}">
            <views:FirstView />
        </DataTemplate>
    </Application.Resources>

به کمک این DataTemplate‌ها مشخص شده اگر نوع داده‌ی ما از یک نوع View-Model خاص می‌باشد View مناسب را به ازای آن Render کند. با تعریف DataTemplate‌ها در App.Xaml می‌توان از آنها در سطح نرم افزار استفاده کرد. می‌توان DataTemplate‌ها را جهت خلوت کردن App.xaml به Resource دیگری انتقال داد.
دریافت مثال : TwoViews.zip 

 
بازخوردهای دوره
بررسی سیستم جدید گرید بوت استرپ 3
کلاس‌های Grid System و alert نسبت به هم display متفاوتی دارن و شکل صحیح استفاده اش به این صورت می‌باشد:
<div class="container">
    <div class="alert alert-info">
        <h4>
          ایجاد فاصله بین ستون‌ها
        </h4>
    </div>
    <div class="row">
        <div class="col-lg-3 col-sm-4">
            <div class="alert alert-danger" role="alert">
                ستون اول
            </div>
        </div>
        <div class="col-lg-8 col-lg-offset-1 col-sm-7 col-sm-offset-1">
            <div class="alert alert-success" role="alert">
                ستون دوم
            </div>
        </div>
    </div>
    <!-- end row -->
</div>
البته شیوه ای هم که شما استفاده کردین درست کار می‌کنه و بیشتر بنظر میرسه که مشکل از bootstrap فارسی که استفاده میکنین باشه و به صورت صیحیح فارسی سازی نشده باشه float  کلاس‌های row  و Grid system  هارو چک کنین
مطالب
React 16x - قسمت 14 - طراحی یک گرید - بخش 4 - پویاسازی تعاریف ستون‌ها
در گریدی که تا به اینجا طراحی کردیم، اگر قرار باشد بجای جدول فیلم‌ها، جدول مشتری‌ها نمایش داده شود، چکار باید کرد؟ با پیاده سازی فعلی، باید کل تعاریف MoviesTable را در کامپوننت دیگری مانند CustomersTable تکرار کنیم. به همین جهت برای پویاسازی تعاریف ستون‌ها نیاز است این قسمت را از جدول اصلی جدا کرده و به کامپوننت مستقلی مانند tableHeader منتقل کنیم.


ایجاد کامپوننت جدید tableHeader

برای پویاسازی تعاریف ستون‌ها و همچنین کم کردن مسئولیت‌های کامپوننت MoviesTable، فایل جدید src\components\common\tableHeader.jsx را ایجاد می‌کنیم تا در برگیرنده‌ی کامپوننت جدید TableHeader شود. پس از ایجاد این فایل، با استفاده از میانبرهای imrc و cc، ساختار ابتدایی کامپوننت TableHeader را تشکیل می‌دهیم. سپس به کامپوننت MoviesTable بازگشته و متد raiseSort آن‌را cut و به اینجا منتقل می‌کنیم. همچنین نیاز است کل thead جدول فیلم‌ها را نیز به اینجا منتقل کنیم. اما چون می‌خواهیم این تعاریف پویا باشند، باید امکان تعریف پویای ستون‌ها را نیز به آن اضافه کنیم. بنابراین اینترفیس این کامپوننت به صورت زیر است:
- ورودی‌های آن: آرایه‌ی ستون‌های جدول و همچنین شیء sortColumn و رخ‌داد onSort که در متد raiseSort استفاده می‌شوند.

با این توضیحات، کامپوننت TableHeader چنین شکلی را پیدا می‌کند:
import React, { Component } from "react";

class TableHeader extends Component {
  raiseSort = path => {
    console.log("raiseSort", path);
    const sortColumn = { ...this.props.sortColumn };
    if (sortColumn.path === path) {
      sortColumn.order = sortColumn.order === "asc" ? "desc" : "asc";
    } else {
      sortColumn.path = path;
      sortColumn.order = "asc";
    }
    this.props.onSort(sortColumn);
  };

  render() {
    return (
      <thead>
        <tr>
          {this.props.columns.map(column => (
            <th onClick={() => this.raiseSort(column.path)}>{column.label}</th>
          ))}
        </tr>
      </thead>
    );
  }
}

export default TableHeader;
در ابتدای آن، متد raiseSort را از کامپوننت MoviesTable به اینجا منتقل کرده‌ایم.
سپس در متد رندر آن، بر اساس آرایه‌ی columns که از props این کامپوننت دریافت خواهد شد، لیست thهای هدر را به صورت پویا رندر می‌کنیم. در اینجا ساختار مورد نیاز شیء column را نیز مشاهده می‌کنید. نیاز است یک برچسب نمایش داده شود و همچنین برای اینکه this.raiseSort نیز بتواند مجددا کار کند، نیاز است نام خاصیتی که قرار است مرتب سازی بر اساس آن انجام شود نیز مشخص باشد. بنابراین تا اینجا شیء column باید دارای دو خاصیت label و path باشد.

پس از تعریف ابتدایی کامپوننت TableHeader، به کامپوننت MoviesTable بازگشته و شروع به استفاده‌ی از آن می‌کنیم:
import TableHeader from "./common/tableHeader";

در ادامه باید آرایه‌ی columns را که به صورت props به کامپوننت TableHeader ارسال می‌شود، تعریف و مقدار دهی کنیم که تشکیل شده‌است از اشیایی با خواص path و label:
  columns = [
    { path: "title", label: "Title" },
    { path: "genre.name", label: "Genre" },
    { path: "numberInStock", label: "Stock" },
    { path: "dailyRentalRate", label: "Rate" },
    {},
    {}
  ];
در اینجا دو شیء خالی را نیز در انتهای لیست مشاهده می‌کنید که به thهای خالی مانند نمایش Like و دکمه‌ی Delete اشاره می‌کنند.
اکنون می‌توان کل تعریف thead موجود در این کامپوننت را به طور کامل با کامپوننت TableHeader ای که import کردیم، جایگزین کنیم:
  render() {
    const { movies, onDelete, onLike, onSort, sortColumn } = this.props;

    return (
      <table className="table">
        <TableHeader
          columns={this.columns}
          sortColumn={sortColumn}
          onSort={onSort}
        />
        <tbody>
در اینجا ویژگی‌های مورد نیاز جهت تامین props کامپوننت TableHeader نیز ذکر شده‌اند. this.columns را که در همین کامپوننت تعریف کردیم، sortColumn و onSort هم جزو props ارسالی به کامپوننت جاری هستند.

در این حالت اگر برنامه را اجرا کنید، بدون مشکل خروجی نهایی را رندر می‌کند؛ اما در کنسول توسعه دهندگان مرورگر یک چنین خطایی را نیز لاگ خواهد کرد:
index.js:1375 Warning: Each child in a list should have a unique "key" prop.
Check the render method of `TableHeader`. See https://fb.me/react-warning-keys for more information.
در حین تعریف رندر لیست thها در کامپوننت TableHeader، ذکر ویژگی key را فراموش کرده‌ایم. البته در اینجا می‌توان از column.path به‌عنوان key استفاده کرد، اما چون در آرایه‌ی ستون‌ها دو شیء خالی را نیز در انتهای لیست داریم، بهتر است برای این‌ها یک id را نیز تعریف کردیم تا بتوان آن‌ها را به صورت منحصربفردی شناسایی کرد:
class MoviesTable extends Component {
  columns = [
    { path: "title", label: "Title" },
    { path: "genre.name", label: "Genre" },
    { path: "numberInStock", label: "Stock" },
    { path: "dailyRentalRate", label: "Rate" },
    { key: "like" },
    { key: "delete" }
  ];
سپس متد رندر کامپوننت TableHeader را جهت درج key به روز رسانی می‌کنیم:
  render() {
    return (
      <thead>
        <tr>
          {this.props.columns.map(column => (
            <th
              key={column.path || column.key}
              style={{ cursor: "pointer" }}
              onClick={() => this.raiseSort(column.path)}
            >
              {column.label}
            </th>
          ))}
        </tr>
      </thead>
    );
دراینجا اگر column.path مقدار دهی شده بود، از آن استفاده می‌شود، در غیراینصورت از مقدار column.key، به عنوان مقدار ویژگی خاصیت key هر المان th، استفاده خواهد شد.


استخراج TableBody از جدول کامپوننت MoviesTable

اکنون با استخراج TableHeader از کامپوننت MoviesTable، به همان مشکل مخلوط بودن درجه‌ی abstractions رسیده‌ایم. از یک طرف با یک abstraction سطح بالا مانند TableHeader در این کامپوننت سر و کار داریم و از طرف دیگر، نمایش تمام جزئیات درونی رندر جدول نیز پیش روی ما است. همچنین رندر ستون‌های آن نیز پویا نیست و هنوز بر اساس خاصیت this.columns تعریف شده، واکنش نشان نمی‌دهد. به همین جهت tbody این جدول را نیز به یک کامپوننت مستقل تبدیل می‌کنیم. برای این منظور فایل جدید src\components\common\tableBody.jsx را اضافه می‌کنیم. سپس با استفاده از میانبرهای imrc و cc، ساختار ابتدایی کامپوننت TableBody را تشکیل می‌دهیم.
این کامپوننت قرار است آرایه‌ای از اشیاء را دریافت و ردیف‌هایی را بر اساس آن‌ها رندر کند. به همین جهت این آرایه را از props و با نام data دریافت می‌کنیم. نام data به عمد انتخاب شده‌است، تا بیانگر عمومی بودن آن باشد؛ بجای استفاده از نام ویژه‌ی آرایه‌ی movies، در این مثال خاص.
import React, { Component } from "react";

class TableBody extends Component {
  render() {
    const { data, columns } = this.props;

    return (
      <tbody>
        {data.map(item => (
          <tr>
            {columns.map(column => (
              <td></td>
            ))}
          </tr>
        ))}
      </tbody>
    );
  }
}

export default TableBody;
تا اینجا ساختار ابتدایی کامپوننت TableBody را مشاهده می‌کنید که هدف آن، رندر پویای قسمت tbody جدول است. این کامپوننت ابتدا نیاز دارد تا data را از props دریافت کند و بر اساس آن، لیست tr‌ها را رندر کند. سپس هر tr نیز از چندین td تشکیل می‌شود. به همین جهت به لیست دومی به نام columns، برای رندر پویای tdها نیاز است.


رندر محتویات هر سلول جدول به صورت پویا

در این مرحله می‌خواهیم محتویات tdها را رندر کنیم و حالت فعلی آن‌ها یک چنین شکلی را داشته و در آن ارجاع مستقیمی به شیء movie و خواص آن وجود دارد:
{movies.map(movie => (
  <tr key={movie._id}>
    <td>{movie.title}</td>
به علاوه این tdها به رندر دکمه‌ی Like و Delete که المان‌های سفارشی نیز محسوب می‌شوند، ختم شده‌اند.
برای رندر خواص اشیاء آرایه‌ی ارسالی به کامپوننت TableBody، می‌توان از روش [] برای دسترسی به مقادیر خواص استفاده کرد که سبب رندر پویای این مقادیر می‌شود:
<td>{item[column.path]}</td>
مشکل! روش item[column.path] با خاصیتی مانند "genre.name" که یک خاصیت تو در تو است، کار نمی‌کند. به همین جهت نیاز به متد زیر، برای انجام اینکار است:
  getPropValue(obj, path) {
    if (!path) {
      return obj;
    }

    const properties = path.split(".");
    return this.getPropValue(obj[properties.shift()], properties.join("."));
  }
بنابراین تا اینجا روش رندر مقدار هر خاصیت به صورت زیر تغییر می‌کند:
<td>{getPropValue(item, column.path)}</td>
 این تغییر می‌تواند 4 ستون اول را بدون مشکل رندر کند. اما برای مثال در ستون پنجم، کامپوننت Like قرار گرفته‌است. برای نمایش آن باید چکار کرد؟
همانطور که در ابتدای این سری نیز بررسی کردیم، عبارات JSX در نهایت به اشیاء خالص جاوا اسکریپتی ترجمه می‌شوند. این ویژگی در حین تعریف المان‌های سفارشی مانند کامپوننت Like نیز صادق است. به همین جهت در آرایه‌ی columns که تعاریف ستون‌های جدول را به همراه دارد، می‌توان یک خاصیت جدید را تعریف و به آن عبارات JSX را انتساب داد. بنابراین تعاریف tdهای Like و Delete را به طور کامل cut کرده و به خاصیت جدید content این دو شیء خالی انتهای لیست آرایه‌ی columns انتساب می‌دهیم:
class MoviesTable extends Component {
  columns = [
    { path: "title", label: "Title" },
    { path: "genre.name", label: "Genre" },
    { path: "numberInStock", label: "Stock" },
    { path: "dailyRentalRate", label: "Rate" },
    {
      key: "like",
      content: movie => (
        <Like liked={movie.liked} onClick={() => this.props.onLike(movie)} />
      )
    },
    {
      key: "delete",
      content: movie => (
        <button
          onClick={() => this.props.onDelete(movie)}
          className="btn btn-danger btn-sm"
        >
          Delete
        </button>
      )
    }
  ];
البته در اینجا جهت مقدار دهی اشیایی مانند movie، بجای استفاده‌ی مستقیم از یک React element، از یک arrow function استفاده کرده‌ایم تا movie را دریافت کند و یک المان React را بازگشت دهد. همچنین پیشتر از متغیرهای onLike و onDelete در کدهای tdها استفاده کرده بودیم که در ابتدای متد رندر تعریف شده بودند؛ اما زمانیکه این قطعات کد را به خاصیت content منتقل می‌کنیم، دیگر شناسایی نمی‌شوند. بنابراین در اینجا برای دسترسی به آن‌ها، مستقیما از props استفاده می‌شود.

مرحله‌ی بعد، مراجعه به کامپوننت tableBody و استفاده از خاصیت جدید content، جهت رندر محتوای آن است. در اینجا در متد renderCell بررسی می‌کنیم اگر ستونی دارای خاصیت content باشد، آن content را رندر می‌کنیم. در غیراینصورت از همان getPropValue متداول استفاده خواهد شد:
  renderCell = (item, column) => {
    if (column.content) {
      return column.content(item);
    }

    return this.getPropValue(item, column.path);
  };

  createKey = (item, column) => {
    return item._id + (column.path || column.key);
  };

  render() {
    const { data, columns } = this.props;

    return (
      <tbody>
        {data.map(item => (
          <tr key={item._id}>
            {columns.map(column => (
              <td key={this.createKey(item, column)}>
                {this.renderCell(item, column)}
              </td>
            ))}
          </tr>
        ))}
      </tbody>
    );
  }
- در متد renderCell، فراخوانی column.content(item) با توجه به function بودن content تعریف شده‌ی در آرایه‌ی columns، در حقیقیت یک عبارت JSX را بازگشت می‌دهد که در خروجی‌های متدهای React مجاز است و در نهایت تبدیل به المان‌های خالص جاوا اسکریپتی در DOM مجازی React و در نهایت DOM اصلی مرورگر می‌شوند.
- همچنین در اینجا یک createKey را نیز مشاهده می‌کنید. المان‌های هر Array.map نوشته شده، نیاز به یک ویژگی key مقدار دهی شده دارند که در دو قسمت trها و همچنین tdها تعریف شده‌است. در فرمول آن جائیکه از || استفاده شده، اگر ستونی دارای path بود، مقدار آن درج می‌شود، اما اگر مانند دو ستون آخر صرفا key تعریف شده بود، وجود || سبب می‌شود تا column.key درنظر گرفته شود و مشکلی رخ ندهد.
- علت تعریف دو متد مجزای renderCell و createKey هم کم شدن بار if/elseها، در بین کدهای درج شده‌ی در ردیف‌های جدول است.

اکنون به کامپوننت MoviesTable مراجعه کرده و کل tbody آن‌را حذف و با المان کامپوننت TableBody، جایگزین می‌کنیم:
//...
import TableBody from "./common/tableBody";
//...

class MoviesTable extends Component {
  // ...

  render() {
    const { movies, onSort, sortColumn } = this.props;

    return (
      <table className="table">
        <TableHeader
          columns={this.columns}
          sortColumn={sortColumn}
          onSort={onSort}
        />
        <TableBody columns={this.columns} data={movies} />
      </table>
    );
  }
}
تا اینجا اگر این تغییرات را ذخیره کرده و برنامه را مجددا در مرورگر بارگذاری کنیم، باید به همان خروجی قبلی برسیم؛ که اینبار تعاریف ستون‌های آن پویا شده‌است.


اضافه کردن آیکن مرتب سازی اطلاعات به سر ستون‌های جدول

در کامپوننت tableHeader، کار رندر thها انجام می‌شود. در اینجا پس از نام سرستون، می‌خواهیم آیکن نمایش صعودی و یا نزولی بودن روش مرتب سازی جاری را نمایش دهیم. برای این منظور، ابتدا متد renderSortIcon را به این کامپوننت اضافه می‌کنیم:
  renderSortIcon = column => {
    const { sortColumn } = this.props;

    if (column.path !== sortColumn.path) {
      return null;
    }

    if (sortColumn.order === "asc") {
      return <i className="fa fa-sort-asc" />;
    }

    return <i className="fa fa-sort-desc" />;
  };
این متد، شیء column در حال رندر را دریافت کرده و بر اساس sortColumn دریافتی از props و همچنین صعودی و یا نزولی بودن روش مرتب سازی، یکی از آیکن‌های font-awesome را به صورت یک المان جدید رندر می‌کند. اگر این column در حال رندر، با sortColumn تعیین شده یکی نبود، آیکنی رندر نمی‌شود (با بازگشت نال، هیچ چیزی رندر نخواهد شد).
و سپس در متد رندر کامپوننت tableHeader، این متد را در کنار label آن ستون درج خواهیم کرد:
{column.label} {this.renderSortIcon(column)}
پس از ذخیره سازی تغییرات و بارگذاری مجدد برنامه در مرورگر، خروجی آن‌را برای نمونه به صورت یک آیکن مثلثی شکل، در کنار عنوان Title می‌توان مشاهده کرد:



استخراج کل Table از جدول کامپوننت MoviesTable

در حال حاضر اگر به پیاده سازی کامپوننت MoviesTable دقت کنیم، یک تگ table به همراه دو کامپوننت TableHeader و TableBody در آن درج شده‌اند. با این طراحی، اگر قصد استفاده‌ی از این امکانات را در جای دیگری داشته باشیم، باید دقیقا همین قطعه کد را تکرار کنیم. به همین جهت کل تگ table این کامپوننت را استخراج کرده و به کامپوننت جدیدی منتقل می‌کنیم. به همین جهت فایل جدید src\components\common\table.jsx را ایجاد کرده و با استفاده از میانبرهای imrc و cc، ساختار ابتدایی کامپوننت Table را تشکیل می‌دهیم. سپس کل تگ table کامپوننت MoviesTable را cut کرده و به متد رندر کامپوننت جدید Table منتقل می‌کنیم. سپس اولین قدم برای سازگار کردن این محتوا با یک کامپوننت جدید، افزودن importهای زیر است:
import TableBody from "./tableBody";
import TableHeader from "./tableHeader";
سپس باید تمام ویژگی‌های استفاده شده‌ی در این المان منتقل شده را از طریق props دریافت کرد که انجام اینکار را در سطر اول متد رندر مشاهده می‌کنید:
import TableBody from "./tableBody";
import TableHeader from "./tableHeader";

class Table extends Component {
  render() {
    const { columns, sortColumn, onSort, data } = this.props;
    return (
      <table className="table">
        <TableHeader
          columns={columns}
          sortColumn={sortColumn}
          onSort={onSort}
        />
        <TableBody columns={columns} data={data} />
      </table>
    );
  }
}

export default Table;
با این تغییرات به یک کامپوننت ساده‌ی با قابلیت استفاده‌ی مجدد رسیده‌ایم. اکنون المان آن‌را در کامپوننت MoviesTable، در جای تگ قبلی table قرار می‌دهیم:
//...

import Table from "./common/table";

class MoviesTable extends Component {
  //... 

  render() {
    const { movies, onSort, sortColumn } = this.props;

    return (
      <Table
        columns={this.columns}
        sortColumn={sortColumn}
        onSort={onSort}
        data={movies}
      />
    );
  }
}


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:  sample-14.zip
مطالب
توضیح مثالی از SIMD برای نشان دادن عملکرد آن - SIMD Performance
پیشنیازها

SIMD یا ترجمه آن به فارسی به معنی «تک دستورالعمل و چند داده»، قابلیت آن‌را دارد تا بر روی مقادیر عددی به صورت موازی و با استفاده از پردازنده کار کند. اگر بتوانیم ساختار پروژه‌های خود را به طوری ایجاد کنیم تا بتوانیم از SIMD در پردازش‌های خود استفاده کنیم، سرعت انجام فعالیت‌ها، بسیار زیاد افزایش پیدا خواهند کرد؛ به خصوص این امر در حجم‌های پردازشی زیاد محسوس خواهد بود. البته مدیریت استفاده از منابع و پردازنده نباید فراموش شوند.
اطلاعات لازم از SIMD و نحوه عملکرد آن را می‌توانید در مقاله پیشنیاز بیابید. در این مقاله قصد داریم تا یک مثال ساده از کارآیی SIMD را مطرح کنیم. مثال زیر از مثال SimdSpike الگو برداری شده است و تغییراتی نیز جهت تکمیل شدن آن انجام شده است.
در این مثال می‌خواهیم نمونه کدهایی را با روش‌های معمول اجرا کنیم و زمان اجرای آن را با زمان اجرای همان مثال‌ها با روش SIMD، مقایسه کنیم. 
با استفاده از ویژوال استودیو 2015 آپدیت 3 یک پروژه کنسول با چارچوب دات نت 4.6.1 ایجاد کرده‌ایم. البته می‌توانید ازدیگر نسخه‌ها هم استفاده کنید به شرط آنکه دات نت 4.6x را نصب کرده باشید.

در صورتی که ویژوال استودیوی شما دارای این ورژن و آپدیت نبود، می‌توانید چارچوب دات نت 4.6.1 را جداگانه در سیستم خود نصب نمایید. توجه داشته باشید که برای استفاده از چارچوب دات نت در ویژوال استودیو باید نسخه‌های DevPack یا DeveloperPack را نصب نمایید (دریافت  دات نت 4.6.1 نسخه مخصوص استفاده در ویژال استودیو). 

در پروژه ایجاد شده فایلی به نام Program.cs و در آن کلاس Program وجود دارد. در این کلاس تابع شروع کننده برنامه یعنی Main وجود دارد و برنامه از این تابع شروع خواهد شد.

نمایی از فایل‌های پروژه

در تابع شروع کننده برنامه ابتدا وضعیت پشتیبانی از SIMD را چک می‌کنیم. این کار را همانطور که قبلا در مقاله پیشنیاز توضیح داده شده است با استفاده از خاصیت Vector.IsHardwareAccelerated بررسی می‌کنیم. اگر مقدار آن برابر با False باشد به معنای عدم پشتیبانی می‌باشد و با بررسی این موضوع در اول برنامه، در صورت عدم پشتیبانی از SIMD به اجرای ادامه‌ی برنامه خاتمه می‌دهیم.

پس از بررسی وضعیت پشتیبانی از SIMD ، تابعی را که در فایل Utilities.cs نوشته شده است، فراخوانی می‌کنیم. این تابع به بررسی وضعیت تعداد رجیسترهای SIMD و وضعیت انواع نوع‌های داده‌ای در SIMD می‌پردازد. اگر هر نوع داده‌ای از SIMD پشتیبانی کند (که بستگی به نوع پردازنده شما دارد) اندازه هر نوع داده‌ای را در SIMD چاپ می‌کند و در صورت عدم پشتیبانی هر نوع داده‌ای از SIMD مقدار «عدم پشتیبانی SIMD از آن نوع داده‌ای» چاپ خواهد شد. 

  تا به اینجای برنامه کد‌های تابع شروع کننده به صورت زیر خواهد بود. 
using System.Numerics;
using static System.Console;

namespace TestSIMD
{
    class Program
    {
        private const int ArraySize = 7680 * 4320;
        static void Main(string[] args)
        {
            // بررسی وضعیت پشتیبانی از SIMD
            if (!Vector.IsHardwareAccelerated)
            {
                WriteLine("Hardware acceleration not supported.");
                WriteLine();
                return; // عدم پشتیبانی و خاتمه برنامه
            }
            WriteLine("Hardware acceleration is supported"); // اعلام پشتیبانی از SIMD
            WriteLine();

            // بررسی وضعیت نوع‌های داده ای در مشخصات سخت افزاری SIMD
            Utilities.PrintHardwareSpecificSimdEffectiveness();

            //به منظور عدم خروج از برنامه و دیدن نتایج آزمایش
            WriteLine("Press any key to exit");
            ReadKey();
        }
    }
}
اجرای برنامه هم به صورت زیر به نمایش در خواهد آمد. 

در فایل Utilities.cs، توابع دیگری هم وجود دارند که کارآیی هر یک به صورت توضیح در بالای هر تابع نوشته شده است. این توابع برای تولید یک نوع داده‌ای تصادفی و ایجاد آرایه‌ای از نوع داده‌ای به صورت تصادفی به کار برده می‌شوند. می توانید در سورس برنامه این توضیحات را مشاهده کنید.
تا به اینجا تنها به بررسی پشتیبانی سخت افزاری از SIMD پرداختیم و همچنین توانستیم نوع‌های داده‌ای را که SIMD در سخت افزار ما پشتیبانی می‌کند، شناسایی کنیم و اندازه رجیستر‌های آنها را بیابیم.
حال به بررسی عملکرد توابع SIMD می‌پردازیم و با نوشتن چند تابع، زمان اجرای محاسباتی آنها را با نوشتن همان توابع در حالت معمولی و ساده مقایسه می‌کنیم.
 برای انجام مقایسه، زمان اجرای یک عملیات را در حالت معمول، با زمان اجرای همان عملیات در حالت SIMD بررسی می‌کنیم. هر عملیات را 3 مرتبه پشت سر هم اجرا می‌کنیم و زمان آنها را ثبت می‌کنیم تا تفاوت زمان اجرا را با تکرار عملیات نیز مشاهده کنیم. توابعی که آزمایشات را انجام می‌دهند و زمان اجرا را ثبت و نمایش می‌دهند، در فایل PerformanceTests.cs و در کلاس PerformanceTests قرار دارند و از توابع سه کلاس دیگر که عملیات در آن نوشته شده‌اند، استفاده می‌کنند.
  • فایل IntSimdProcessor.cs
    • در این فایل کلاسی به نام IntSimdProcessor قرار دارد که شامل 6 تابع می‌باشد و این تابع‌ها با نوع داده‌ای صحیح یا همان Integer کار می‌کنند. نام کلاس هم به همین خاطر نام گذاری شده است. 
    • این 6 تابع در کل 3 عملیات را شامل عملیات‌های زیر انجام می‌دهند. یکبار در حالت معمولی و یکبار با استفاده از توابع SIMD این کار را انجام می‌دهند:
      • پیدا کردن بزرگترین و کوچکترین عدد در آرایه
      • جمع عناصر دو آرایه با هم با استفاده از یک آرایه کمکی که نتیجه در آرایه کمکی ریخته می‌شود
      • جمع عناصر دو آرایه بدون استفاده از آرایه کمکی که مجموع در آرایه اول ریخته می‌شود
    • در بالای هر تابع در این فایل توضیحات لازم درباره‌ی فعالیت آن تابع ذکر شده است.
 
  • فایل FloatSimdProcessor.cs
    • در این فایل کلاسی با نام FloatSimdProcessor قرار دارد که همانطور که از نام کلاس پیداست، توابعی برای کار بر روی اعداد از نوع داده‌ای float در آن نوشته شده‌اند.
    • در این کلاس هم 6 تابع برای انجام 3 عملیات زیر نوشته شده است که به ازای هر عملیات دو تابع یکی در حالت معمولی و یکی در حالت SIMD نوشته شده است.
      • جمع دو آرایه با استفاده از یک آرایه کمکی - مجموع در آرایه کمکی ریخته می‌شود
      • جمع دو آرایه اول ورودی - مجموع در آرایه سوم ریخته می‌شود
      • جمع دو آرایه بدون استفاده از آرایه کمکی - مجموع در آرایه اول ریخته می‌شود
    • در آزمایشات نوشته شده در کلاس PerformanceTests  تنها از عملیات آخری استفاده شده است و از دو عملیات اول استفاده نشده است که در صورت تمایل می‌توانید از دیگر عملیات‌ها نیز استفاده کنید.
    • در بالای هر تابع در این فایل توضیحات لازم درباره‌ی فعالیت آن تابع نیز ذکر شده است.
 
  • فایل UShortSimdProcessor.cs
    • در این فایل کلاسی با نام UShortSimdProcessor قرار دارد و همانطور که از نام کلاس پیداست، توابعی برای کار بر روی اعداد از نوع داده‌ای ushort یا همان اعداد صحیح کوچک بدون علامت نوشته شده‌اند.
    • در این کلاس 12 تابع برای انجام 6 عملیات زیر نوشته شده‌است که به ازای هر عملیات، دو تابع یکی در حالت معمولی و یکی در حالت SIMD نوشته شده است.
      • جمع دو آرایه اول ورودی که مجموع در آرایه سوم ریخته می‌شود
      • جمع دو آرایه بدون استفاده از آرایه کمکی که مجموع در آرایه اول ریخته می‌شود
      • بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح کوچک بدون علامت
      • جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی
      • جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی بدون بررسی سرریز (Overflow)
      • محاسبه میانگین و بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح کوچک بدون علامت
    • در بالای هر تابع در این فایل توضیحات لازم درباره‌ی فعالیت آن تابع ذکر شده است.
 
حال در کلاس PerformanceTests برای انجام آزمایشات و مقایسه زمان اجرا، 10 تابع وجود دارند که 10 عملیات مختلف را بر روی 3 نوع داده‌ای، اجرا می‌کنند. 3 عملیات از کلاس IntSimdProcessor و یک عملیات از کلاس FloatSimdProcessor و 6 عملیات از کلاس UShortSimdProcessor را مورد آزمایش قرار داده‌ایم که در مجموع شامل 10 آزمایش در 10 تابع مختلف شده است.
public static void TestIntArrayAdditionFunctions(int testSetSize) {
    WriteLine();
    Write("Testing int array addition, generating test data...");
    var intsOne = GetRandomIntArray(testSetSize); //تولید آرایه عددی به صورت تصادفی
    var intsTwo = GetRandomIntArray(testSetSize);
    WriteLine($" done, testing...");// پایان تولید آرایه‌ها و شروع پردازش
    var naiveTimesMs = new List<long>(); // تعریف لیستی برای ریختن زمان پاسخ دهی در حالت ساده و معمولی
    var hwTimesMs = new List<long>(); // تعریف لیستی برای ریختن زمان پاسخ دهی در حالت SIMD و سخت افزاری 
    for (var i = 0; i < 3; i++) { // ایجاد حلقه برای تکرار محاسبات برای اندازه گیری زمان در حالت تکراری
        stopwatch.Restart();//شروع ثبت زمان
        var result = IntSimdProcessor.NaiveSumFunc(intsOne, intsTwo);//اجرای تابع جمع دو آرایه
        var naiveTimeMs = stopwatch.ElapsedMilliseconds;//ثبت زمان
        naiveTimesMs.Add(naiveTimeMs);//افزودن زمان ثبت شده به لیست زمان‌های ساده و معمول
        WriteLine($"Naive analysis took:                {naiveTimeMs}ms (last value = {result.Last()}).");

        stopwatch.Restart();//شروع ثبت زمان
        result = IntSimdProcessor.HWAcceleratedSumFunc(intsOne, intsTwo);//اجرای تابع جمع دو آرایه در حالت سخت افزاری
        var hwTimeMs = stopwatch.ElapsedMilliseconds;//ثبت زمان
        hwTimesMs.Add(hwTimeMs);//افزودن زمان به لیست زمان‌های سخت افزاری
        WriteLine($"Hareware accelerated analysis took: {hwTimeMs}ms (last value = {result.Last()}).");
    }//پایان حلقه و چاپ نتایج
    WriteLine("Int array addition:");
    WriteLine($"Naive method average time:          {naiveTimesMs.Average():.##}");
    WriteLine($"HW accelerated method average time: {hwTimesMs.Average():.##}");
    WriteLine($"Hardware speedup:                   {naiveTimesMs.Average() / hwTimesMs.Average():P}%");
}
در بالا تکه کدی مربوط به تابع آزمایش اول از کلاس PerformanceTests قرار دارد و وظیفه دارد عملیات جمع دو آرایه را با استفاده از یک آرایه کمکی اعداد صحیح، هم در حالت معمولی و هم در حالت SIMD انجام دهد و زمان اجرای آنها را ثبت و نمایش دهد تا بتوانیم این زمان اجرا‌ها را با هم مقایسه کنیم.
ساختار و روند اجرای کلیه آزمایش‌ها و توابع در کلاس PerformanceTests با یکدیگر یکسان است و از یک stopwatch یا همان کرنومتر برای محاسبه زمان اجرا استفاده شده است.
هر کدام از این توابع یک عملیات را مورد بررسی قرار می‌دهند و هر عملیات را 3 مرتبه اجرا می‌کنند تا زمان تکرار اجرا نیز مورد مقایسه قرار گیرد.

نام تابع ذکر شده نشان دهنده آزمایش بر روی آرایه اعداد صحیح یا همان Integer می‌باشد که شامل یک پارامتر ورودی از نوع عدد صحیح می‌باشد. این پارامتر ورودی نشان دهنده اندازه هر آرایه‌ای می‌باشد که قرار است تولید شود.  

TestIntArrayAdditionFunctions(int testSetSize)

در قدم اول این تابع، باید آرایه‌ها را تولید کنیم که کد آن به صورت زیر است.

Write("Testing int array addition, generating test data...");
var intsOne = GetRandomIntArray(testSetSize);
var intsTwo = GetRandomIntArray(testSetSize);
WriteLine($" done, testing...");

ابتدا در خروجی چاپ می‌کنیم که در حال ایجاد داده‌های مربوط به آزمایش هستیم و سپس با استفاده از تابع GetRandomIntArray آرایه‌ای را ایجاد می‌کنیم و در متغیر‌های مربوطه می‌ریزیم. این تابع دارای یک پارامتر ورودی از نوع عدد صحیح است که آرایه‌ای را به طول پارامتر ورودی تولید می‌کند. این تابع در فایل Utilities.cs قرار دارد.

در پایان تولید آرایه‌ها، اتمام تولید و ایجاد آرایه‌ها را با چاپ در خروجی اعلام میکنیم.

سپس با معرفی دو لیست زیر می‌توانیم زمان‌های اجرا را در آنها بریزیم و در پایان، تابع میانگین این زمان‌ها را محاسبه و چاپ کنیم. لیست اول برای نگهداری زمان‌های اجرای عملیات در حالت معمولی و لیست دوم برای نگهداری زمانهای اجرای عملیات در حالت SIMD می‌باشد.

var naiveTimesMs = new List<long>();
var hwTimesMs = new List<long>();

سپس با ایجاد حلقه ای از 0 تا 3 که در کل 3 مرتبه اجرا می‌شود عملیات را تکرار و زمان آن را ثبت می‌کنیم. 

for (var i = 0; i < 3; i++)

درون حلقه یک عملیات را در دوحالت معمولی یا ساده و SIMD اجرا می‌کنیم. قبل از اجرای عملیات اول ابتدا stopwatch را ریست می‌کنیم. با این کار زمان صفر شده و شروع به اندازه گیری می‌کند. سپس عملیات مربوط به جمع دو آرایه را در حالت معمولی که در فایل IntSimdProcessor.cs قرار دارد، فراخوانی می‌کنیم. پس از اجرای این عملیات مقدار stopwatch را به میلی ثانیه در یک متغیر ذخیره میکنیم و این مقدار را به لیست زمان‌های اجرای معمولی اضافه می‌کنیم. در نهایت نتیجه زمان اجرا را در خروجی چاپ می‌کنیم. 

stopwatch.Restart();
var result = IntSimdProcessor.NaiveSumFunc(intsOne, intsTwo);
var naiveTimeMs = stopwatch.ElapsedMilliseconds;
naiveTimesMs.Add(naiveTimeMs);
WriteLine($"Naive analysis took:                {naiveTimeMs}ms (last value = {result.Last()}).");

پس از اجرای عملیات در حالت ساده یا معمولی، حال نوبت همان عملیات در حالت SIMD می‌باشد. دوباره stopwatch را ریست می‌کنیم و عملیات در SIMD را اجرا کرده و بعد از آن مقدار stopwatch را درون متغیری میریزیم و آن را به لیست زمان‌های اجرای عملیات در SIMD اضافه می‌کنیم و در نهایت نتیجه زمان اجرا را در خروجی چاپ می‌کنیم. 

stopwatch.Restart();
result = IntSimdProcessor.HWAcceleratedSumFunc(intsOne, intsTwo);
var hwTimeMs = stopwatch.ElapsedMilliseconds;
hwTimesMs.Add(hwTimeMs);
WriteLine($"Hareware accelerated analysis took: {hwTimeMs}ms (last value = {result.Last()}).");

پس از اجرای حلقه، حال نوبت به نمایش نتیجه میانگین زمان‌ها در خروجی است. ابتدا میانگین زمان‌های اجرا در حالت ساده یا معمولی را که به میلی ثانیه است را در خروجی چاپ می‌کنیم. بعد از آن میانگین زمان‌های اجرا در حالت SIMD را در خروجی چاپ می‌کنیم و در آخر سرعت زمان اجرا در حالت SIMD را نسبت به حالت معمولی به درصد چاپ می‌کنیم. 

WriteLine($"Naive method average time:          {naiveTimesMs.Average():.##}");
WriteLine($"HW accelerated method average time: {hwTimesMs.Average():.##}");
WriteLine($"Hardware speedup:                   {naiveTimesMs.Average() / hwTimesMs.Average():P}%");

در این مقاله تنها به توضیحی در مورد این آزمایش اکتفا می‌کنیم. لازم به ذکر است که دیگر آزمایش‌ها نیز دقیقا ساختاری مشابه این آزمایش را دارند و تنها عملیات اجرا در آنها متفاوت است. در کلاس PerformanceTests توضیحات لازم مربوط به هر آزمایش و تابع داده شده است و می‌توانید با مراجعه به کد برنامه آنها را مورد بررسی قرار دهید.

برای اجرای تمامی آزمایش‌ها، کلیه توابع نوشته شده در کلاس PerformanceTests را در کلاس Program و در تابع Main که تابع شروع کننده برنامه می‌باشد، پس از بررسی وضعیت نوع‌های داده‌ای قرار می‌دهیم.

تصویر مربوط به اجرای کامل برنامه را می‌توانید مشاهده می‌کنید. 

این جدول بر اساس یک بار اجرای برنامه در سیستم من ترسیم شده است و اجرای برنامه در سیستم‌های مختلف خروجی‌های متفاوتی را دارد. لازم به ذکر است که اندازه آرایه‌ها بسیار بزرگ است و این نتایج با آرایه‌هایی به طول بیش از هزاران هزار عنصر می‌باشد.

زمان‌ها در جدول به میلی ثانیه می‌باشد.

ردیف

عملیات

دور اول

دور دوم

دور سوم

میانگین حالت ساده

میانگین حالت SIMD

درحالت ساده

درحالت SIMD

درحالت ساده

درحالت SIMD

درحالت ساده

درحالت SIMD

1

جمع دو آرایه با استفاده از یک آرایه کمکی در اعداد صحیح

157

131

128

131

128

138

137.67

133.33

2

جمع دو آرایه بدون استفاده از آرایه کمکی در اعداد float

122

133

99

99

99

93

106.67

108.33

3

جمع دو آرایه بدون استفاده از آرایه کمکی در اعداد صحیح

83

73

86

88

78

81

82.33

80.67

4

جمع دو آرایه اول ورودی - مجموع در آرایه سوم ریخته می‌شود - در اعداد صحیح کوچک بدون علامت

58

63

50

48

58

46

55.33

52.33

5

جمع دو آرایه بدون استفاده از آرایه کمکی در اعداد صحیح کوچک بدون علامت

55

40

53

36

53

46

53.67

40.67

6

بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح

91

36

91

39

90.67

38

90.66

38

7

بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح کوچک بدون علامت

90

20

89

19

88

18

89

19

8

جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی

33

309

32

263

31

291

32

287.67

9

جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی بدون بررسی سرریز

30

13

29

13

30

12

29.67

12.67

10

محاسبه میانگین و بدست آوردن کمترین و بیشترین مقدار در آرایه اعداد صحیح کوچک بدون علامت

89

50

90

51

90

49

89.57

50



سورس کامل برنامه را که شامل تغییراتی در توابع برای بهبود و اضافه شدن کامنت برای فهم بیشتر کدها می‌باشد، در زیر می‌توانید دریافت کنید: 
   TestSIMD.zip  

مطالب
ویژگی های کمتر استفاده شده در NET. - بخش دوم

Curry and Partial methods

Curry – در ریاضیات و علوم کامپیوتر، currying روشی است برای ترجمه تابعی که آرگومان‌های متعددی می‌گیرد و به صورت ارزیابی دنباله‌ای‌است از توابع که هر کدام یک آرگومان دارند.
برای پیاده سازی آن در #C، از extension methods استفاده می‌کنیم.
public static class CurryMethodExtensions
{
    public static Func< A, Func< B, Func< C, R > > > Curry< A, B, C, R >( this Func< A, B, C, R > f )
    {
        return a => b => c => f( a, b, c );
    }
}
مثالی برای استفاده از متد بالا:
Func< int, int, int, int > addNumbers = ( x, y, z ) => x + y + z;
var f1 = addNumbers.Curry();
Func< int, Func< int, int > > f2 = f1( 3 );
Func< int, int > f3 = f2( 4 );
Console.WriteLine( f3( 5 ) );
بعد از فراخوانی متد Curry می‌توان از کلمه کلیدی var در دستورات بعدی بجای تعریف نوع متغیرها استفاده کرد.
نحوه اجرای دستورات بالا را در تصویر زیر می‌توانید مشاهده کنید:


Partial – در علوم کامپیوتر، قسمتی از یک برنامه (یا قسمتی از یک تابع برنامه) است که اشاره به روند تثبیت تعدادی از آرگومان‌ها به یک تابع و تولید تعداد آرگومان‌های کمتر تابع دیگری را می‌گویند.
public static class CurryMethodExtensions
{
    public static Func< C, R > Partial< A, B, C, R >( this Func< A, B, C, R > f, A a, B b )
    {
        return c => f( a, b, c );
    }
}
مثالی برای استفاده از تابع بالا:
Func< int, int, int, int > sumNumbers = ( x, y, z ) => x + y + z;
Func< int, int > f4 = sumNumbers.Partial( 3, 4 );
Console.WriteLine( f4( 5 ) );
بعد از فراخوانی متد Curry می‌توان از کلمه کلیدی var در دستورات بعدی بجای تعریف نوع متغیرها استفاده کرد.
نحوه اجرای دستورات بالا را در تصویر زیر می‌توانید مشاهده کنید:

WeakReference

یک ارجاع ضعیف به GC اجازه می‌دهد که یک شیء را جمع آوری کند، در عین حالی که برنامه امکان دسترسی به آن را خواهد داشت. در صورتیکه نیاز به شیء‌ای داشته باشید، می‌توانید یک ارجاع قوی را از آن داشته باشید و از جمع آوری آن توسط GC جلوگیری کنید.
var obj = new WeakReferenceTest
            {
                FirstName = "Vahid"
            };
var w = new WeakReference(obj);
obj = null;
GC.Collect();
var weakReferenceTest = w.Target as WeakReferenceTest;
if ( weakReferenceTest != null )
    Console.WriteLine( weakReferenceTest.FirstName );

Lazy<T>

برای ایجاد یک شیء بزرگ، پردازش زیاد منابع و یا اجرای یک وظیفه (task) با پردازش زیاد منابع، به خصوص در زمانیکه ایجاد و یا اجرای این فرآیند در طول عمر یک برنامه، ممکن است هرگز رخ ندهد، از Lazy استفاده می‌شود.
public abstract class ThreadSafeLazyBaseSingleton< T > where T : new()
{
    static readonly Lazy< T > lazy = new Lazy< T >( () => new T() );

    public static T Instance => lazy.Value;
}

BigInteger

نوع داده BigInteger یک نوع تغییر ناپذیر (immutable type) و نمایانگر یک عدد صحیح بزرگ دلخواه است که مقدار آن در تئوری در هیچ حد و مرز حداقل و حداکثری نیست. این نوع، از دیگر انواع جدایی ناپذیر (integral types) در NET.، که دارای خصوصیت MinValue و  MaxValue هستند، متفاوت است.
var positiveString = "91389681247993671255433422114345532000000";
var negativeString = "-9031583741089631207100208803453423537140000";
var posBigInt = BigInteger.Parse( positiveString );
Console.WriteLine( posBigInt );
var negBigInt = BigInteger.Parse( negativeString );
Console.WriteLine( negBigInt );
نکته: از آنجایی که BigInteger یک نوع تغییر ناپذیر و بدون حد و مرز حداقل و حداکثر است، برای بعضی از عملیات‌، اگر مقدار آن را بسیار زیاد افزایش دهید خطای OutOfMemoryException رخ می‌دهد (البته من با 1024 بار ضرب متغیر positiveString در خودش هم نتوانستم این پیام خطا را ببینم).