জেনারিক্স বনাম সাধারণ ইন্টারফেস?


20

আমার মনে নেই কখন আমি জেনেরিক ক্লাসটি শেষবার লিখলাম। যতবার আমি ভাবি আমার কিছুটা চিন্তা করার পরে এটি দরকার আমি একটি উপসংহারটি করি না।

এই প্রশ্নের দ্বিতীয় উত্তর আমাকে স্পষ্টতা জিজ্ঞাসা করতে বাধ্য করেছে (যেহেতু আমি এখনও মন্তব্য করতে পারি না, তাই আমি একটি নতুন প্রশ্ন করেছি)।

সুতরাং আসুন আমরা উদাহরণস্বরূপ কোডটি গ্রহণ করি যেখানে জেনেরিকের প্রয়োজন হয়:

public class Repository<T> where T : class, IBusinessOBject
{
  T Get(int id)
  void Save(T obj);
  void Delete(T obj);
}

এটির ধরণের বাধা রয়েছে: IBusinessObject

আমার স্বাভাবিক চিন্তাভাবনাটি হ'ল: ক্লাসটি ব্যবহারে সীমাবদ্ধ IBusinessObject, সুতরাং যে ক্লাসগুলি এটি ব্যবহার করে সেগুলিও Repository। সংগ্রহস্থলগুলি এইগুলি সংরক্ষণ করে IBusinessObject, সম্ভবত এর ক্লায়েন্টরা ইন্টারফেসের Repositoryমাধ্যমে অবজেক্টগুলি পেতে এবং ব্যবহার করতে চাইবে IBusinessObject। সুতরাং কেন শুধু না

public class Repository
{
  IBusinessOBject Get(int id)
  void Save(IBusinessOBject obj);
  void Delete(IBusinessOBject obj);
}

থো, উদাহরণটি ভাল নয়, কারণ এটি কেবলমাত্র অন্য সংগ্রহের ধরণ এবং জেনেরিক সংগ্রহটি ক্লাসিক। এক্ষেত্রে টাইপের সীমাবদ্ধতাও অদ্ভুত দেখাচ্ছে।

প্রকৃতপক্ষে উদাহরণটি আমার সাথে class Repository<T> where T : class, IBusinessbBjectদেখতে অনেকটা মিলে class BusinessObjectRepository। জেনারিকগুলি ঠিক করার জন্য তৈরি জিনিসটি।

পুরো কথাটি হ'ল: জেনেরিকগুলি সংগ্রহ ব্যতীত যে কোনও কিছুর জন্য ভাল এবং টাইপের সীমাবদ্ধতাগুলি জেনেরিককে বিশেষ হিসাবে বিশেষ হিসাবে তৈরি করা হয় না, কারণ শ্রেণীর অভ্যন্তরে জেনেরিক ধরণের প্যারামিটারের পরিবর্তে এই ধরণের সীমাবদ্ধতাটি ব্যবহার করে?

উত্তর:


24

আসুন প্রথমে খাঁটি প্যারামেট্রিক পলিমারফিজম সম্পর্কে কথা বলি এবং পরে আবদ্ধ পলিমারফিজমে যাই।

প্যারামেট্রিক পলিমারফিজম

প্যারামেট্রিক পলিমারফিজম বলতে কী বোঝায়? ঠিক আছে, এর অর্থ একটি প্রকার, বা বরং টাইপ কনস্ট্রাক্টর একটি টাইপ দ্বারা প্যারামিটারাইজড । যেহেতু প্রকারটি পরামিতি হিসাবে প্রেরণ করা হয়েছে, তাই এটি কী হতে পারে তা আপনি আগেই জানতে পারবেন না। আপনি এর উপর ভিত্তি করে কোনও অনুমান করতে পারবেন না। এখন, আপনি যদি না জানেন তবে এটি কী হতে পারে, তবে এর ব্যবহার কী? আপনি এটি দিয়ে কি করতে পারেন?

ঠিক আছে, আপনি এটি সংরক্ষণ এবং পুনরুদ্ধার করতে পারেন, উদাহরণস্বরূপ। আপনি ইতিমধ্যে উল্লিখিত কেস: সংগ্রহ। একটি তালিকা বা একটি অ্যারেতে কোনও আইটেম সংরক্ষণ করার জন্য, আইটেমটি সম্পর্কে আমার কিছু জানতে হবে না। তালিকা বা অ্যারে সম্পূর্ণ ধরণের হতে পারে না।

তবে কি Maybeধরণের? আপনি যদি এটির সাথে পরিচিত না হন তবে এটি Maybeএমন এক ধরণের যা সম্ভবত একটি মান এবং সম্ভবত নাও। আপনি এটি কোথায় ব্যবহার করবেন? ঠিক আছে, উদাহরণস্বরূপ, অভিধান থেকে কোনও আইটেম বের করার সময়: কোনও আইটেম অভিধানে নাও থাকতে পারে এটি একটি ব্যতিক্রমী পরিস্থিতি নয়, সুতরাং আইটেমটি না থাকলে আপনার সত্যিকার অর্থে ব্যতিক্রম করা উচিত নয়। পরিবর্তে, আপনি একটি উপপ্রকারের উদাহরণটি ফিরে আসবেন Maybe<T>, যার ঠিক দুটি উপপ্রকার রয়েছে: Noneএবং Some<T>int.Parseএমন কোনও Maybe<int>কিছুর আর একজন প্রার্থী যা ব্যতিক্রম বা পুরো int.TryParse(out bla)নাচ ছুঁড়ে ফেলার পরিবর্তে সত্যিকার অর্থে ফিরে আসা উচিত ।

এখন, আপনি তর্ক করতে পারেন যে Maybeএকটি তালিকা মত কিন্ডার সাজ্টা যা শুধুমাত্র শূন্য বা একটি উপাদান থাকতে পারে। এবং এইভাবে একটি সংগ্রহ কিন্ডার-বাছা।

তাহলে কি হবে Task<T>? এটি এমন এক প্রকার যা ভবিষ্যতে কোনও সময়ে কোনও মান ফেরত দেওয়ার প্রতিশ্রুতি দেয়, তবে এখনই অগত্যা কোনও মান নেই have

বা কি Func<T, …>? আপনি যদি কোনও ধরণের উপর বিমূর্ত করতে না পারেন তবে আপনি কীভাবে কোনও ফাংশনের ধারণাকে এক প্রকার থেকে অন্য প্রকারে উপস্থাপন করবেন?

বা আরও সাধারণভাবে: বিমূর্ততা এবং পুনঃব্যবহারটি সফ্টওয়্যার ইঞ্জিনিয়ারিংয়ের দুটি মূল ক্রিয়াকলাপ বিবেচনা করে আপনি কেন প্রকারভেদে বিমূর্ত করতে সক্ষম হবেন না ?

বাউন্ডেড পলিমারফিজম

সুতরাং, আসুন এখন বাউন্ডেড পলিমারফিজম সম্পর্কে কথা বলি। বাউন্ডেড পলিমারফিজম মূলত যেখানে প্যারামেট্রিক পলিমারফিজম এবং সাব টাইপ পলিমারফিজম মিলিত হয়: কোনও টাইপ কনস্ট্রাক্টর তার টাইপ প্যারামিটার সম্পর্কে পুরোপুরি অসচেতন হওয়ার পরিবর্তে, আপনি টাইপটিকে কিছু নির্দিষ্ট ধরণের সাব টাইপ হিসাবে বেঁধে (বা সীমাবদ্ধ করতে পারেন )।

সংগ্রহগুলিতে ফিরে যাই। একটি হ্যাশটেবল নিন। আমরা উপরে বলেছি যে একটি তালিকার উপাদানগুলির সম্পর্কে কিছু জানার দরকার নেই। ঠিক আছে, একটি হ্যাশটেবল রয়েছে: এটি তাদের হ্যাশ করতে পারে তা জানতে হবে। (দ্রষ্টব্য: সি # তে সমস্ত বস্তু হ্যাশযোগ্য, ঠিক যেমন সমস্ত বস্তুর সমতার জন্য তুলনা করা যায়। যদিও এটি সমস্ত ভাষার ক্ষেত্রে সত্য নয়, এমনকি কখনও কখনও সি # তেও এটি ডিজাইনের ভুল হিসাবে বিবেচিত হয়))

সুতরাং, আপনি হ্যাশ টেবিলের মূল ধরণের জন্য আপনার টাইপ পরামিতিটিকে সীমাবদ্ধ করতে চান IHashable:

class HashTable<K, V> where K : IHashable
{
  Maybe<V> Get(K key);
  bool Add(K key, V value);
}

পরিবর্তে আপনার যদি এটি থাকে তা কল্পনা করুন:

class HashTable
{
    object Get(IHashable key);
    bool Add(IHashable key, object value);
}

valueআপনি সেখান থেকে সরে যাওয়ার সাথে কি করবেন ? আপনি এটি দিয়ে কিছুই করতে পারবেন না, আপনি কেবল এটি জানেন যে এটি একটি অবজেক্ট। এবং যদি আপনি এটির উপরে পুনরাবৃত্তি করেন তবে আপনার উত্পন্ন সমস্ত কিছুই আপনার জানা জিনিসগুলির একটি জুড়ি IHashable(যা আপনাকে খুব বেশি সহায়তা করে না কারণ এটির কেবল একটি সম্পত্তি রয়েছে Hash) এবং আপনি objectযা জানেন সেগুলি হ'ল (যা আপনাকে আরও কম সহায়তা করে)।

বা আপনার উদাহরণের ভিত্তিতে কিছু:

class Repository<T> where T : ISerializable
{
    T Get(int id);
    void Save(T obj);
    void Delete(T obj);
}

আইটেমটি সিরিয়ালযোগ্য হওয়া দরকার কারণ এটি ডিস্কে সঞ্চিত হতে চলেছে। তবে এর পরিবর্তে আপনার যদি এটি থাকে:

class Repository
{
    ISerializable Get(int id);
    void Save(ISerializable obj);
    void Delete(ISerializable obj);
}

জেনেরিক মামলা, আপনি একটি করা BankAccount, আপনি একটি পেতে BankAccountপিছনে, পদ্ধতি ও বৈশিষ্ট্য মতো Owner, AccountNumber, Balance, Deposit, Withdraw, ইত্যাদি কিছু আপনার সাথে কাজ করতে পারেন। এখন, অন্য মামলা? আপনি একটি রাখা BankAccountকিন্তু আপনি ফিরে পেতে Serializableযা মাত্র এক সম্পত্তি আছে,: AsString। আপনি কি করতে যাচ্ছেন?

সীমাবদ্ধ পলিমারফিজম দিয়ে আপনি করতে পারেন এমন কিছু ঝরঝরে কৌশলও রয়েছে:

এফ-সীমাবদ্ধ পলিমারফিজম

এফ-সীমাবদ্ধ কোয়ান্টিফিকেশন মূলত যেখানে টাইপ ভেরিয়েবল আবার সীমাবদ্ধতার মধ্যে উপস্থিত হয়। এটি কিছু পরিস্থিতিতে কার্যকর হতে পারে। যেমন আপনি কিভাবে একটি ICloneableইন্টারফেস লিখবেন ? আপনি কীভাবে এমন একটি পদ্ধতি লিখবেন যেখানে রিটার্নের ধরণটি প্রয়োগকারী শ্রেণীর ধরণ? মাইটাইপ বৈশিষ্ট্যযুক্ত ভাষায় এটি সহজ:

interface ICloneable
{
    public this Clone(); // syntax I invented for a MyType feature
}

সীমিত পলিমারফিজমযুক্ত ভাষায়, আপনি পরিবর্তে এর মতো কিছু করতে পারেন:

interface ICloneable<T> where T : ICloneable<T>
{
    public T Clone();
}

class Foo : ICloneable<Foo>
{
    public Foo Clone()
    {
        // …
    }
}

মনে রাখবেন যে এটি মাইটাইপ সংস্করণ হিসাবে নিরাপদ নয়, কারণ টাইপ কনস্ট্রাক্টরকে কাউকে কেবল "ভুল" শ্রেণিটি পাস করা থেকে বিরত করার কিছুই নেই:

class EvilBar : ICloneable<SomethingTotallyUnrelatedToBar>
{
    public SomethingTotallyUnrelatedToBar Clone()
    {
        // …
    }
}

বিমূর্ত প্রকারের সদস্যগণ

দেখা যাচ্ছে, আপনার যদি অ্যাবস্ট্রাক্ট টাইপের সদস্য এবং সাব টাইপিং থাকে তবে আপনি প্যারামেট্রিক পলিমারফিজম ছাড়াই সম্পূর্ণরূপে পেতে পারেন এবং এখনও একই জিনিসগুলি করতে পারেন। স্ক্যালালা এই দিকে এগিয়ে চলেছে, জেনারিক দিয়ে শুরু হওয়া প্রথম প্রধান ভাষা এবং সেগুলি মুছে ফেলার চেষ্টা করছে যা জাভা এবং সি # থেকে অন্যভাবে way

মূলত, স্কালায়, যেমন আপনার সদস্য হিসাবে ক্ষেত্র এবং সম্পত্তি এবং পদ্ধতি থাকতে পারে, আপনার ধরণেরও থাকতে পারে। এবং ঠিক যেমন ক্ষেত্র এবং বৈশিষ্ট্য এবং পদ্ধতিগুলি পরবর্তীতে একটি সাবক্লাসে প্রয়োগ করার জন্য বিমূর্ততা রেখে যেতে পারে, টাইপ সদস্যদেরও বিমূর্ত রাখতে পারেন। আসুন সংগ্রহগুলিতে ফিরে যাই, একটি সরল List, এটি সি # তে সমর্থিত হলে এমন কিছু দেখায়:

class List
{
    T; // syntax I invented for an abstract type member
    T Get(int index) { /* … */ }
    void Add(T obj) { /* … */ }
}

class IntList : List
{
    T = int;
}
// this is equivalent to saying `List<int>` with generics

আমি বুঝতে পারি, প্রকারভেদে বিমূর্ততা কার্যকর। আমি কেবল "বাস্তব জীবনে" এর ব্যবহার দেখতে পাচ্ছি না। Func <> এবং কার্য <> এবং ক্রিয়া <> লাইব্রেরির ধরণ। এবং ধন্যবাদ আমি সম্পর্কেও মনে আছে interface IFoo<T> where T : IFoo<T>। এটি অবশ্যই বাস্তব জীবনের আবেদন। উদাহরণটি দুর্দান্ত। তবে কিছু কারণে আমি সন্তুষ্ট বোধ করি না। আমি বরং আমার মনটি কখন এটি উপযুক্ত এবং কখন তা নয় তা ঘিরে রাখতে চাই। উত্তরগুলির এই প্রক্রিয়াটিতে কিছুটা অবদান রয়েছে, তবে আমি এখনও নিজেকে এই সমস্ত বিষয়ে অযোগ্য মনে করছি। এটি আশ্চর্যজনক কারণ ভাষা-স্তরের সমস্যাগুলি ইতিমধ্যে এত দিন আমাকে বিরক্ত করে না।
জঙ্গল_মোল

দুর্দান্ত উদাহরণ। আমার মনে হচ্ছিল আমি ক্লাস রুমে ফিরে এসেছি। +1
শেফ_ কোড

1
@Chef_Code: আমি আশা করি একটি প্রশংসা :-P যে
Jörg ডব্লিউ Mittag

হ্যাঁ, তাই আমি পরে চিন্তা করেছি যে আমি ইতিমধ্যে মন্তব্য করার পরে এটি কীভাবে অনুধাবন করা যায়। আন্তরিকতা নিশ্চিত করতে ... হ্যাঁ এটি প্রশংসা অন্য কিছু নয়।
শেফ_কোড

14

পুরো কথাটি হ'ল: জেনেরিকগুলি সংগ্রহ ব্যতীত যে কোনও কিছুর জন্য ভাল এবং টাইপের সীমাবদ্ধতাগুলি জেনেরিককে বিশেষ হিসাবে বিশেষ হিসাবে তৈরি করা হয় না, কারণ শ্রেণীর অভ্যন্তরে জেনেরিক ধরণের প্যারামিটারের পরিবর্তে এই ধরণের সীমাবদ্ধতাটি ব্যবহার করে?

নং আপনি অত্যধিক সম্পর্কে চিন্তা করছি Repository, যেখানে এটি হয় প্রায় কাছাকাছি একই। তবে জেনারিকরা সেটির জন্য যা তা নয়। তারা সেখানে ব্যবহারকারীদের জন্য রয়েছে

এখানে মূল বক্তব্যটি নয় যে रिपোজিটরি নিজেই আরও জেনেরিক। এটি ব্যবহারকারীরা আরও বিশেষজ্ঞ-যেমন, Repository<BusinessObject1>এবং Repository<BusinessObject2>বিভিন্ন ধরণের এবং আরও, আমি যদি এটি গ্রহণ করি তবে Repository<BusinessObject1>আমি জানি যে আমি BusinessObject1এখান থেকে ফিরে আসব Get

আপনি সাধারণ উত্তরাধিকার থেকে এই শক্তিশালী টাইপিং অফার করতে পারবেন না। আপনার প্রস্তাবিত সংগ্রহশালা শ্রেণি বিভিন্ন ধরণের ব্যবসায়ের উদ্দেশ্যে লোককে বিভ্রান্তকারী ভাণ্ডারগুলিকে আটকাতে বা সঠিক ধরণের ব্যবসায়ের অবজেক্টটি সামনে আসার নিশ্চয়তা দেওয়ার জন্য কিছুই করে না।


ধন্যবাদ, এটি উপলব্ধি করে। কিন্তু এই অতি প্রশংসিত ভাষা বৈশিষ্ট্যটি ব্যবহার করার পুরো বিষয়টি কি এমন ব্যবহারকারীদের পক্ষে সহায়তা করা যেমন ইন্টেলিসেন্স বন্ধ আছে? (আমি কিছুটা অতিরঞ্জিত করছি, তবে আমি নিশ্চিত যে আপনি পয়েন্টটি পেয়েছেন)
জঙ্গল_মোল

@ জ্লোইডুরাক: এছাড়াও ইন্টেলিজেন্স জানে না কোন ধরণের অবজেক্টস কোনও ভাণ্ডারে সংরক্ষণ করা হয়। তবে হ্যাঁ, আপনি যদি এর পরিবর্তে কাস্ট ব্যবহার করতে ইচ্ছুক হন তবে আপনি জেনারিকগুলি ছাড়াই কিছু করতে পারেন।
জেক্সিকাইড

@ জিক্সাইডাইড এই বিষয়টি: আমি যদি সাধারণ ইন্টারফেস ব্যবহার করি তবে ক্যাসেটগুলি কোথায় ব্যবহার করতে হবে তা আমি দেখতে পাচ্ছি না। আমি কখনও "ব্যবহার Object" বলিনি । এছাড়াও আমি বুঝতে পারি কেন সংগ্রহগুলি লেখার সময় জেনেরিক ব্যবহার করা হয় (ডিআরওয়াই নীতি)। সম্ভবত, আমার প্রাথমিক প্রশ্নটি সংগ্রহের প্রেক্ষাপটের বাইরে
জেনেরিকগুলি

@ জ্লোডুরাক: পরিবেশের সাথে এর কোনও যোগসূত্র নেই। ইনটেলিসেন্স আপনাকে বলতে পারে না যে IBusinessObjectএটি একটি BusinessObject1বা একটি BusinessObject2। এটি যে জেনে নেই তার ভিত্তিতে ওভারলোডগুলি সমাধান করতে পারে না। এটি ভুল ধরণের পাসের কোডটিকে প্রত্যাখ্যান করতে পারে না। আরও শক্তিশালী টাইপিংয়ের এক মিলিয়ন বিট রয়েছে যা ইন্টেলিসেন্স একেবারে কিছুই করতে পারে না। উন্নত টুলিং সমর্থন একটি দুর্দান্ত উপকারী তবে মূল কারণগুলির সাথে আসলে কিছুই করার নেই।
ডেড এমজি

@ ডিডএমজি এবং এটি আমার বক্তব্য: ইন্টেলিজেন্স এটি করতে পারে না: জেনেরিক ব্যবহার করুন, তাই এটি পারে? এটা কোন ব্যাপার? আপনি যখন তার ইন্টারফেসের মাধ্যমে অবজেক্টটি পেয়েছেন, কেন এটি ডাউনস্টাস্ট করবেন? আপনি যদি এটি করেন, এটি খারাপ নকশা, না? এবং কেন এবং "সমাধানের ওভারলোডগুলি" কী? ব্যবহারকারীর অবশ্যই সিদ্ধান্ত নিতে হবে না, কল পদ্ধতিটি কিনা ডেরাইভেড টাইপের ভিত্তিতে নয়, যদি তিনি সিস্টেমে সঠিক পদ্ধতির কলটি অর্পণ করেন (যা বহুবর্ষ হয়)। এবং এটি আমাকে আবার একটি প্রশ্নের দিকে নিয়ে যায়: জেনেরিকগুলি পাত্রে বাইরে কী কার্যকর? আমি আপনার সাথে তর্ক করছি না, আমার এটি সত্যই বুঝতে হবে।
জঙ্গল_মোল

13

"সম্ভবত এই সংগ্রহস্থলের ক্লায়েন্টরা আইব্যাসনেসবজেক্ট ইন্টারফেসের মাধ্যমে অবজেক্টগুলি পেতে এবং ব্যবহার করতে চাইবে"।

না, তারা করবে না।

আসুন বিবেচনা করুন যে আইবসনেসঅবজেক্টের নিম্নলিখিত সংজ্ঞা রয়েছে:

public interface IBusinessObject
{
  public int Id { get; }
}

এটি কেবল আইডিটি সংজ্ঞায়িত করে কারণ এটি সমস্ত ব্যবসায়িক সামগ্রীর মধ্যে একমাত্র ভাগ করা কার্যকারিতা। এবং আপনার দুটি প্রকৃত ব্যবসায়ের অবজেক্ট রয়েছে: ব্যক্তি এবং ঠিকানা (যেহেতু ব্যক্তিদের রাস্তাগুলি এবং ঠিকানাগুলির নাম নেই, কারণ আপনি উভয়কেই দুর্বোধ্যতার সাথে একটি কমপ ইন্টারফেসে সীমাবদ্ধ রাখতে পারবেন না That এটি একটি ভয়ানক নকশা হবে, লঙ্ঘন করে ইন্টারফেস Segragation পুঁজি , "আমি" এ কঠিন )

public class Person : IBusinessObject
{
  public int Id { get; private set; }
  public string Name { get; private set; }
}

public class Address : IBusinessObject
{
  public int Id { get; private set; }
  public string City { get; private set; }
  public string StreetName { get; private set; }
  public int Number { get; private set; }
}

এখন, আপনি যখন সংগ্রহস্থলের জেনেরিক সংস্করণ ব্যবহার করেন তখন কী হয়:

public class Repository<T> where T : class, IBusinessObject
{
  T Get(int id)
  void Save(T obj);
  void Delete(T obj);
}

আপনি যখন জেনেরিক সংগ্রহস্থলে get পদ্ধতি কল করবেন তখন ফিরে আসা অবজেক্টটি শক্তভাবে টাইপ করা হবে, যা আপনাকে সমস্ত শ্রেণীর সদস্যদের অ্যাক্সেস করার অনুমতি দেয়।

Person p = new Repository<Person>().Get(1);
int id = p.Id;
string name = p.Name;

Address a = new Repository<Address>().Get(1);
int id = a.Id;
string cityName = a.City;
int houseNumber = a.Number;

অন্যদিকে, আপনি অ-জেনেরিক সংগ্রহস্থল ব্যবহার করার সময়:

public class Repository
{
  IBusinessOBject Get(int id)
  void Save(IBusinessOBject obj);
  void Delete(IBusinessOBject obj);
}

আপনি কেবল আইব্যাসনেসবজেক্ট ইন্টারফেস থেকে সদস্যদের অ্যাক্সেস করতে সক্ষম হবেন:

IBussinesObject p = new Repository().Get(1);
int id = p.Id; //OK
string name = p.Name; //Oooops, you dont have "Name" defined on the IBussinesObject interface.

সুতরাং, আগের কোডগুলি নিম্নলিখিত লাইনের কারণে সংকলন করবে না:

string name = p.Name;
string cityName = a.City;
int houseNumber = a.Number;

অবশ্যই, আপনি আসল ক্লাসে আইবুসিনসঅবজেক্টটি কাস্ট করতে পারেন, তবে জেনেরিকরা অনুমতি দেয় এমন সমস্ত সংকলন টাইম ম্যাজিক হারাবে (রাস্তায় অবৈধপ্রেম অনুভবের দিকে পরিচালিত করে), অকারণে ওভারহেডে ভুগবে ... এবং এমনকি যদি আপনি না করেন তবে সংকলন সময় সম্পর্কে পারফরম্যান্স (আপনার হওয়া উচিত) যাচাইয়ের বিষয়ে যত্ন নেওয়া, পরে কাস্টিং অবশ্যই প্রথম স্থানে জেনেরিক ব্যবহারের ক্ষেত্রে আপনাকে কোনও সুবিধা দেয় না।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.