আউট প্যারামিটার সহ একটি অ্যাসিঙ্ক পদ্ধতি কীভাবে লিখবেন?


176

আমি outএই জাতীয় প্যারামিটার সহ একটি অ্যাসিঙ্ক পদ্ধতি লিখতে চাই:

public async void Method1()
{
    int op;
    int result = await GetDataTaskAsync(out op);
}

আমি এটি কিভাবে করব GetDataTaskAsync?

উত্তর:


279

আপনি ASYNC সঙ্গে পদ্ধতি থাকতে পারে না refবা outপ্যারামিটার।

লুসিয়ান Wischik ব্যাখ্যা দিয়েছে কেন এই দুটিই MSDN থ্রেডে সম্ভব নয়: http://social.msdn.microsoft.com/Forums/en-US/d2f48a52-e35a-4948-844d-828a1a6deb74/why-async-methods-cannot-have -ref-অর-আউট-পরামিতি

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

এই পরিস্থিতির জন্য একটি সাধারণ কাজ হ'ল অ্যাসিঙ্ক পদ্ধতিটি পরিবর্তে একটি টুপল ফিরিয়ে আনা। আপনি নিজের পদ্ধতিটি আবার লিখতে পারেন:

public async Task Method1()
{
    var tuple = await GetDataTaskAsync();
    int op = tuple.Item1;
    int result = tuple.Item2;
}

public async Task<Tuple<int, int>> GetDataTaskAsync()
{
    //...
    return new Tuple<int, int>(1, 2);
}

10
খুব জটিল হওয়ার থেকে দূরে, এটি অনেক বেশি সমস্যা তৈরি করতে পারে। জন স্কিট এটা খুব ভাল এখানে ব্যাখ্যা stackoverflow.com/questions/20868103/...
MuiBienCarlota

3
Tupleবিকল্প জন্য ধন্যবাদ । খুব উপকারী.
লুক ভিও

19
এটা কুৎসিত হচ্ছে Tuple। : পি
tofutim

36
আমি মনে করি সি # 7 এ নামযুক্ত টিপলস এর জন্য উপযুক্ত সমাধান হবে।
ওরাড

3
@orad আমি বিশেষত এটি পছন্দ করেছি: বেসরকারী অ্যাসিঙ্ক টাস্ক <(বুল সাফল্য, চাকরির চাকরী, স্ট্রিং বার্তা)> ট্রাইজেটজব্যাসেন্স (...)
জে অ্যান্ড্রু

51

আপনার পদ্ধতিতে refবা outপরামিতি থাকতে পারে না async(যেমনটি ইতিমধ্যে উল্লেখ করা হয়েছিল)।

এই ডেটা ঘুরে বেড়াতে কিছু মডেলিংয়ের জন্য চিৎকার করে:

public class Data
{
    public int Op {get; set;}
    public int Result {get; set;}
}

public async void Method1()
{
    Data data = await GetDataTaskAsync();
    // use data.Op and data.Result from here on
}

public async Task<Data> GetDataTaskAsync()
{
    var returnValue = new Data();
    // Fill up returnValue
    return returnValue;
}

আপনি আপনার কোডটি আরও সহজে পুনরায় ব্যবহার করার ক্ষমতা অর্জন করবেন এবং এর সাথে ভেরিয়েবল বা টিপলসের চেয়ে এটি আরও বেশি পঠনযোগ্য।


2
আমি একটি সমাধান ব্যবহার না করে এই সমাধানটি পছন্দ করি। আরো পরিষ্কার!
MiBol

31

সি # 7 + সলিউশন অন্তর্ভুক্ত টিউপল সিনট্যাক্স ব্যবহার করা।

    private async Task<(bool IsSuccess, IActionResult Result)> TryLogin(OpenIdConnectRequest request)
    { 
        return (true, BadRequest(new OpenIdErrorResponse
        {
            Error = OpenIdConnectConstants.Errors.AccessDenied,
            ErrorDescription = "Access token provided is not valid."
        }));
    }

রিটার্ন ফলাফল পদ্ধতি স্বাক্ষর সংজ্ঞায়িত সম্পত্তি নাম ব্যবহার করে। উদাহরণ:

var foo = await TryLogin(request);
if (foo.IsSuccess)
     return foo.Result;

12

অ্যালেক্স পঠনযোগ্যতার উপর একটি দুর্দান্ত পয়েন্ট করেছিলেন। সমানভাবে, একটি ফাংশনটি প্রকার (গুলি) ফেরত দেওয়া সংজ্ঞা দিতে যথেষ্ট ইন্টারফেস এবং আপনি অর্থপূর্ণ পরিবর্তনশীল নামও পান।

delegate void OpDelegate(int op);
Task<bool> GetDataTaskAsync(OpDelegate callback)
{
    bool canGetData = true;
    if (canGetData) callback(5);
    return Task.FromResult(canGetData);
}

কলকারীরা একটি ল্যাম্বদা (বা একটি নামযুক্ত ফাংশন) সরবরাহ করে এবং ইন্টেলিজেন্স প্রতিনিধি থেকে পরিবর্তনশীল নাম (গুলি) অনুলিপি করে সহায়তা করে।

int myOp;
bool result = await GetDataTaskAsync(op => myOp = op);

এই নির্দিষ্ট পদ্ধতিটি "চেষ্টা" পদ্ধতির মতো যেখানে myOpপদ্ধতির ফলাফল থাকলে সেট করা থাকে true। অন্যথায়, আপনি যত্ন নেই myOp


9

outপ্যারামিটারগুলির একটি দুর্দান্ত বৈশিষ্ট্য হ'ল কোনও ফাংশন ব্যতিক্রম ছুঁড়ে ফেলা সত্ত্বেও এগুলি ডেটা ফেরত দিতে ব্যবহৃত হতে পারে। আমি মনে করি যে কোনও asyncপদ্ধতিতে এটি করার নিকটতম সমতুল্য কোনও asyncপদ্ধতি এবং কলার উভয়ই উল্লেখ করতে পারে এমন ডেটা ধরে রাখতে একটি নতুন অবজেক্ট ব্যবহার করবে । আরেকটি উপায় হ'ল অন্য উত্তরের পরামর্শ অনুসারে একজন প্রতিনিধিকে পাশ করা

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

এখানে অনুকরণ করার একটি ভাগ বস্তু ব্যবহার করে একটি উদাহরণ বাস্তবায়ন এর refএবং outসাথে ব্যবহারের জন্য asyncপদ্ধতি এবং অন্যান্য বিভিন্ন পরিস্থিতিতে যেখানে refএবং outউপস্থিত নেই:

class Ref<T>
{
    // Field rather than a property to support passing to functions
    // accepting `ref T` or `out T`.
    public T Value;
}

async Task OperationExampleAsync(Ref<int> successfulLoopsRef)
{
    var things = new[] { 0, 1, 2, };
    var i = 0;
    while (true)
    {
        // Fourth iteration will throw an exception, but we will still have
        // communicated data back to the caller via successfulLoopsRef.
        things[i] += i;
        successfulLoopsRef.Value++;
        i++;
    }
}

async Task UsageExample()
{
    var successCounterRef = new Ref<int>();
    // Note that it does not make sense to access successCounterRef
    // until OperationExampleAsync completes (either fails or succeeds)
    // because there’s no synchronization. Here, I think of passing
    // the variable as “temporarily giving ownership” of the referenced
    // object to OperationExampleAsync. Deciding on conventions is up to
    // you and belongs in documentation ^^.
    try
    {
        await OperationExampleAsync(successCounterRef);
    }
    finally
    {
        Console.WriteLine($"Had {successCounterRef.Value} successful loops.");
    }
}

6

আমি Tryপ্যাটার্নটি পছন্দ করি। এটি একটি পরিপাটি বিন্যাস।

if (double.TryParse(name, out var result))
{
    // handle success
}
else
{
    // handle error
}

তবে, এটি চ্যালেঞ্জিং async। এর অর্থ এই নয় যে আমাদের কাছে আসল বিকল্প নেই। এর তিনটি asyncঅর্ধ সংস্করণে পদ্ধতির জন্য আপনি যে তিনটি মূল পদ্ধতির বিবেচনা করতে পারেন তা এখানেTryপ্যাটার্নটির ।

পদ্ধতির 1 - আউটপুট একটি কাঠামো

সবচেয়ে একটি সিঙ্ক মত এই সৌন্দর্য Tryপদ্ধতি শুধুমাত্র একটি ফিরে tupleএকটি পরিবর্তে boolএকটি সঙ্গে outপরামিতি, যা আমরা সবাই জানি C # অনুমোদিত নয়।

var result = await DoAsync(name);
if (result.Success)
{
    // handle success
}
else
{
    // handle error
}

একটি পদ্ধতি যে আয় দিয়ে trueএর falseএবং কখনও একটি ছোঁড়ার exception

মনে রাখবেন, কোনও Tryপদ্ধতিতে একটি ব্যতিক্রম ছুঁড়ে দেওয়া প্যাটার্নটির পুরো উদ্দেশ্যটি ভেঙে দেয়।

async Task<(bool Success, StorageFile File, Exception exception)> DoAsync(string fileName)
{
    try
    {
        var folder = ApplicationData.Current.LocalCacheFolder;
        return (true, await folder.GetFileAsync(fileName), null);
    }
    catch (Exception exception)
    {
        return (false, null, exception);
    }
}

পদ্ধতির 2 - কলব্যাক পদ্ধতিতে পাস করুন

anonymousবাহ্যিক ভেরিয়েবলগুলি সেট করার জন্য আমরা পদ্ধতিগুলি ব্যবহার করতে পারি । এটি কিছুটা জটিল হলেও চালাক সিনট্যাক্স। ছোট মাত্রায় এটি ঠিক আছে।

var file = default(StorageFile);
var exception = default(Exception);
if (await DoAsync(name, x => file = x, x => exception = x))
{
    // handle success
}
else
{
    // handle failure
}

পদ্ধতিটি Tryপ্যাটার্নের বেসিকগুলি মান্য করে তবে outকলব্যাক পদ্ধতিতে পাস হওয়া পরামিতিগুলি সেট করে। এটি এইভাবে সম্পন্ন হয়েছে।

async Task<bool> DoAsync(string fileName, Action<StorageFile> file, Action<Exception> error)
{
    try
    {
        var folder = ApplicationData.Current.LocalCacheFolder;
        file?.Invoke(await folder.GetFileAsync(fileName));
        return true;
    }
    catch (Exception exception)
    {
        error?.Invoke(exception);
        return false;
    }
}

এখানে পারফরম্যান্স সম্পর্কে আমার মনে একটি প্রশ্ন রয়েছে। তবে, সি # সংকলকটি এত স্মার্ট হ'ল, আমি মনে করি আপনি প্রায় নিশ্চিতভাবেই এই বিকল্পটি বেছে নিচ্ছেন।

পদ্ধতির 3 - চালিয়ে যাওয়া ব্যবহার করুন

আপনি যদি কেবল TPLডিজাইন হিসাবে ব্যবহার করেন ? টিপলস নেই। এখানে ধারণাটি হ'ল আমরা ContinueWithদুটি পৃথক পথে পুনঃনির্দেশ করতে ব্যতিক্রমগুলি ব্যবহার করি ।

await DoAsync(name).ContinueWith(task =>
{
    if (task.Exception != null)
    {
        // handle fail
    }
    if (task.Result is StorageFile sf)
    {
        // handle success
    }
});

এমন কোনও পদ্ধতির সাহায্যে exceptionযখন কোনও ধরণের ব্যর্থতা হয়। এটি ফিরে আসার চেয়ে আলাদা boolean। এটি যোগাযোগের উপায় TPL

async Task<StorageFile> DoAsync(string fileName)
{
    var folder = ApplicationData.Current.LocalCacheFolder;
    return await folder.GetFileAsync(fileName);
}

উপরের কোডে, ফাইলটি পাওয়া না গেলে একটি ব্যতিক্রম ছুঁড়ে দেওয়া হবে। এটি ব্যর্থতা ডেকে ContinueWithআনবে Task.Exceptionযা এর লজিক ব্লকে পরিচালনা করবে । ঝরঝরে, হাহ?

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

ভাগ্য সুপ্রসন্ন হোক.


1
তৃতীয় পদ্ধতির সম্পর্কে, আপনি কি নিশ্চিত যে চেইন ContinueWithকলগুলির প্রত্যাশিত ফলাফল রয়েছে? আমার বোঝা অনুযায়ী দ্বিতীয়টি ContinueWithপ্রথম কাজটির সাফল্য পরীক্ষা করবে, মূল কাজটির সাফল্য নয়।
থিওডর জৌলিয়াস

1
চিয়ারস @ থিডোরজৌলিয়াস, এটি একটি তীক্ষ্ণ চোখ। সংশোধন করা হয়েছে।
জেরি নিকসন

1
ফ্লো কন্ট্রোলের জন্য ব্যতিক্রম ছোঁড়া আমার পক্ষে একটি বিশাল কোড গন্ধ - এটি আপনার কার্য সম্পাদনকে টানতে চলেছে।
ইয়ান কেম্প

না, @ ইয়ানকেম্প, এটি একটি পুরানো ধারণা। সংকলকটি বিকশিত হয়েছে।
জেরি নিকসন

4

আমার চেষ্টা-পদ্ধতি-প্যাটার্নটি ব্যবহার করার মতো একই সমস্যা ছিল যা মূলত অ্যাসিঙ্ক-অপেক্ষার-দৃষ্টান্তের সাথে বেমানান বলে মনে হচ্ছে ...

আমার কাছে গুরুত্বপূর্ণ এটি হ'ল আমি যদি এক-ইফ-ক্লজের মধ্যে চেষ্টা পদ্ধতিটি কল করতে পারি এবং এর আগে আউট-ভেরিয়েবলগুলি প্রাক-সংজ্ঞায়িত করতে না হয় তবে নীচের উদাহরণের মতো এটি ইন-লাইনে করতে পারি:

if (TryReceive(out string msg))
{
    // use msg
}

সুতরাং আমি নিম্নলিখিত সমাধান নিয়ে এসেছি:

  1. একটি সহায়ক কাঠামো সংজ্ঞায়িত করুন:

     public struct AsyncOut<T, OUT>
     {
         private readonly T returnValue;
         private readonly OUT result;
    
         public AsyncOut(T returnValue, OUT result)
         {
             this.returnValue = returnValue;
             this.result = result;
         }
    
         public T Out(out OUT result)
         {
             result = this.result;
             return returnValue;
         }
    
         public T ReturnValue => returnValue;
    
         public static implicit operator AsyncOut<T, OUT>((T returnValue ,OUT result) tuple) => 
             new AsyncOut<T, OUT>(tuple.returnValue, tuple.result);
     }
  2. অ্যাসিঙ্ক ট্রাই-পদ্ধতিটি এর মতো সংজ্ঞায়িত করুন:

     public async Task<AsyncOut<bool, string>> TryReceiveAsync()
     {
         string message;
         bool success;
         // ...
         return (success, message);
     }
  3. এ্যাসিঙ্ক ট্রাই-পদ্ধতিটিকে এভাবে কল করুন:

     if ((await TryReceiveAsync()).Out(out string msg))
     {
         // use msg
     }

একাধিক আউট প্যারামিটারগুলির জন্য আপনি অতিরিক্ত স্ট্রাক্ট সংজ্ঞায়িত করতে পারেন (উদাঃ AsyncOut <T, OUT1, OUT2>) বা আপনি একটি টুপল ফিরে আসতে পারেন।


এটি খুব চালাক সমাধান!
থিওডর জৌলিয়াস

2

প্যারামিটারগুলি asyncগ্রহণ না করার পদ্ধতিগুলির সীমাবদ্ধতা outকেবলমাত্র সংকলক দ্বারা উত্পাদিত অ্যাসিঙ্ক পদ্ধতিগুলিতে প্রযোজ্য, এগুলি asyncকীওয়ার্ড সহ ঘোষিত । এটি হস্ত-নকশা করা অ্যাসিঙ্ক পদ্ধতিতে প্রয়োগ হয় না। অন্য কথায় পরামিতিগুলি Taskগ্রহণ করে ফিরে আসা পদ্ধতিগুলি তৈরি করা সম্ভব out। উদাহরণস্বরূপ বলতে দিন যে আমাদের ইতিমধ্যে একটি ParseIntAsyncপদ্ধতি রয়েছে যা ছোঁড়ে এবং আমরা এমন একটি তৈরি করতে চাই TryParseIntAsyncযা ছুঁড়ে না। আমরা এটি এর মতো বাস্তবায়ন করতে পারি:

public static Task<bool> TryParseIntAsync(string s, out Task<int> result)
{
    var tcs = new TaskCompletionSource<int>();
    result = tcs.Task;
    return ParseIntAsync(s).ContinueWith(t =>
    {
        if (t.IsFaulted)
        {
            tcs.SetException(t.Exception.InnerException);
            return false;
        }
        tcs.SetResult(t.Result);
        return true;
    }, default, TaskContinuationOptions.None, TaskScheduler.Default);
}

TaskCompletionSourceএবং ContinueWithপদ্ধতিটি ব্যবহার করা কিছুটা বিশ্রীজনক, তবে আমরা awaitএই পদ্ধতির অভ্যন্তরে সুবিধাজনক কীওয়ার্ডটি ব্যবহার করতে পারি না বলে অন্য কোনও বিকল্প নেই ।

ব্যবহারের উদাহরণ:

if (await TryParseIntAsync("-13", out var result))
{
    Console.WriteLine($"Result: {await result}");
}
else
{
    Console.WriteLine($"Parse failed");
}

আপডেট: যদি awaitঅ্যাসিঙ্ক যুক্তিটি প্রকাশ না করেই জটিল হয় তবে এটি কোনও নেস্টেড অ্যাসিনক্রোনাস বেনামে প্রতিনিধিটির অভ্যন্তরে আবদ্ধ হতে পারে। প্যারামিটারের TaskCompletionSourceজন্য এখনও একটি প্রয়োজন হবে out। এটি সম্ভব হয় যে outপ্যারামিটারটি মূল টাস্কটি শেষ হওয়ার আগেই সম্পন্ন হতে পারে, যেমন উদাহরণস্বরূপ:

public static Task<string> GetDataAsync(string url, out Task<int> rawDataLength)
{
    var tcs = new TaskCompletionSource<int>();
    rawDataLength = tcs.Task;
    return ((Func<Task<string>>)(async () =>
    {
        var response = await GetResponseAsync(url);
        var rawData = await GetRawDataAsync(response);
        tcs.SetResult(rawData.Length);
        return await FilterDataAsync(rawData);
    }))();
}

এই উদাহরণটিতে তিন অ্যাসিঙ্ক্রোনাস পদ্ধতির অস্তিত্ব অনুমান GetResponseAsync, GetRawDataAsyncএবং FilterDataAsyncযে পারম্পর্য মধ্যে বলা হয়। outপরামিতি দ্বিতীয় পদ্ধতি সম্পন্ন সম্পন্ন করা হয়। GetDataAsyncপদ্ধতি এই মত ব্যবহার করা যেতে পারে:

var data = await GetDataAsync("http://example.com", out var rawDataLength);
Console.WriteLine($"Data: {data}");
Console.WriteLine($"RawDataLength: {await rawDataLength}");

প্রতীক্ষমাণ dataঅপেক্ষা সামনে rawDataLengthএই সরলীকৃত উদাহরণে গুরুত্বপূর্ণ একটি ব্যতিক্রম ক্ষেত্রে কারণ হল, outপরামিতি সম্পন্ন করা হবে না।


1
এটি কিছু ক্ষেত্রে খুব সুন্দর সমাধান।
জেরি নিকসন

1

আমি মনে করি এরকম ভ্যালুটুপল ব্যবহার করা কাজ করতে পারে। আপনাকে প্রথমে ভ্যালুআপল নিউগেট প্যাকেজটি যুক্ত করতে হবে যদিও:

public async void Method1()
{
    (int op, int result) tuple = await GetDataTaskAsync();
    int op = tuple.op;
    int result = tuple.result;
}

public async Task<(int op, int result)> GetDataTaskAsync()
{
    int x = 5;
    int y = 10;
    return (op: x, result: y):
}

.Net-4.7 বা নেটস্ট্যান্ডার্ড -০.০ ব্যবহার করা হলে আপনার নিউগেটের দরকার নেই।
বিনকি

আরে, আপনি ঠিক বলেছেন! আমি কেবল সেই নুগেট প্যাকেজটি আনইনস্টল করেছি এবং এটি এখনও কাজ করে। ধন্যবাদ!
পল মারানগনি

1

এখানে @ ড্যাসট্রোর জবাবের কোডটি সি # 7.0 এর জন্য নামযুক্ত টিপলস এবং টুপল ডেকনস্ট্রাকশন দিয়ে সংশোধিত করা হয়েছে যা স্বরলিপিটি প্রবাহিত করে:

public async void Method1()
{
    // Version 1, named tuples:
    // just to show how it works
    /*
    var tuple = await GetDataTaskAsync();
    int op = tuple.paramOp;
    int result = tuple.paramResult;
    */

    // Version 2, tuple deconstruction:
    // much shorter, most elegant
    (int op, int result) = await GetDataTaskAsync();
}

public async Task<(int paramOp, int paramResult)> GetDataTaskAsync()
{
    //...
    return (1, 2);
}

নতুন নামের টিপলস, টুপল আক্ষরিক এবং টিপল ডিকনস্ট্রাকশন সম্পর্কে বিশদ জানতে দেখুন: https ://blogs.msdn.mic Microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/


-2

আপনি অপেক্ষা করুন কীওয়ার্ড ব্যবহার না করে সরাসরি টিপিএল (টাস্ক সমান্তরাল গ্রন্থাগার) ব্যবহার করে এটি করতে পারেন।

private bool CheckInCategory(int? id, out Category category)
    {
        if (id == null || id == 0)
            category = null;
        else
            category = Task.Run(async () => await _context.Categories.FindAsync(id ?? 0)).Result;

        return category != null;
    }

if(!CheckInCategory(int? id, out var category)) return error

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