vkhorikov / csharpfunctionalextensions Goto Github PK
View Code? Open in Web Editor NEWFunctional extensions for C#
License: MIT License
Functional extensions for C#
License: MIT License
In case OnSuccess return IEnumerable<Result> required to use method:
var result = Result.Combine(results);
return result.IsSuccess
? Result.Ok(results.Select(e => e.Value))
: Result.Fail<IEnumerable<T>>(result.Error);
and to do it in OnSuccess method:
.OnSuccess(results => /*code above*/)
It will be nice to have extension methods:
Result<IEnumerable<T>> Combine(this IEnumerable<Result<T>> results)
Result Combine(this IEnumerable<Result> results)
In this case we will have something like:
.OnSuccess(arg => ... ) /* returns IEnumerable<Result<T>> */
.Combine() /* convert IEnumerable<Result<T>> to Result<IEnumerable<T>> */
.OnSuccess(collection => ... )
If we try to open the solution in visual studio 2017, the tests project will not load since it configured via project.json & .xproj. Which need to be migrated to the .csproj format.
With Java streams we have a passive stream operation called "peek" which passes the elements un-touched to the next operation (lambda should return void):
list
.stream()
.peek(element -> log.info("element = " + element))
.map(element -> element is unscathed)
I haven't seen an equivalent in the library. Does one exist? If not, hard to add? It's like ensure but returns void rather than bool?
While tracking a bug if found that Maybe.None.HasValue was true whereas my expected behavior is that any None would have no values.
Is there any way to support structs with Maybe?
I removed the 'where T : class' from the code but then I found this issue:
struct Account{
public Guid Id;
}
Maybe<Account> SomeFunction(){
var list = new List<Account>();
return list.FirstOrDefault()
}
var result = SomeFunction();
//This is true!! it should be false
Assert.IsTrue(result.HasValue)
I want to make Account a struct because I need to use use Entity Framework and its use of references (not immutability) is making my life crappy. So I tried to use structs for value passing, but now I can't seem to use Maybe.
Any suggestions you have would be great. Hope I provided enough info.
Hi, apologies if I've gotten this completely wrong.. I was reading your article: http://enterprisecraftsmanship.com/2015/03/20/functional-c-handling-failures-input-errors/
It discusses the use of the OnFailure extension method for Result, but looking the Nuget package doesn't appear to have this method, the commits on GitHub also don't appear to have the method, am I missing something?
Thanks
Peter
Hi Vladimir,
Currently I am trying to use your CSharpFunctionalExtensions library, but I am getting a name collision when I try to use it as follows.
// Given a function with a signature of Func<string, bool>.
bool IsNotNullOrWhiteSpace(string text) => !string.IsNullOrWhiteSpace(text);
// When I try to use Ensure like this.
// Then the compiler gives a error.
void MyFunction(string text) {
Result.Ok(text)
.Ensure(IsNotNullOrWhiteSpace, "Text is null or white space.")
}
Compiler warning:
The call is ambiguous between the following methods or properties:
'AsyncResultExtensionsRightOperand.Ensure(Result, Func<T, Task>, string)'
and
'ResultExtensions.Ensure(Result, Func<T, bool>, string)'
// This is possible:
void MyWorkAround(string text) {
Result.Ok(text)
.Ensure(t => IsNotNullOrWhiteSpace(t), "Text is null or white space.")
}
It is not a problem, though I prefer the cleaner look of just using the name of my expression bodied function.
I would prefer prefixing async functions with Async to explicitly state their nature and moving them to their own namespace.
If you do not have any objections I could create a pull request unless this behavior is by design?
P.S.
I really enjoyed your PluralSight course and I am looking forward to seeing more from you in the future.
There is some project I cannot use dll file. (Such as Dynamics CRM Plugin)
In this case, source installation is really helpful (like as DryIoC and DryIoC.dll nuget pacakge)
Could you provide source installation Nuget Package?
Hey guys,
First of all a very usefull library :) .
Example
Task<Result<int,ErrorType>> GetSomething(){ ... }
var theResult= await Result.Ok().OnSuccess( () => GetSomething());
theResult will be of type Result<Result<int,ErrorType>>
If the example is not clear, please let me know and I will create a test project
I suppose that the methods in AsyncResultExtensionsRightOperand need to be extended
It would be nice to be able to use an error object without the need to also have a return type for value. Maybe an interface needs to be introduced
I was wondering if this would be a good addition to the Result<T>
class:
public static implicit operator T(Result<T> result) { return result.Value; }
The benefits of this implicit operator removes the need for doing result.Value
in many places. For example:
public TestClass()
{
var result = Result.Ok(new Foo());
//instead of: Foo foo = result.Value
Foo foo = result;
//instead of: MethodThatTakesFoo(result.Value)
MethodThatTakesFoo(result);
}
public void MethodThatTakesFoo(Foo value) { }
The other benefit is you could rename your var
names:
//instead of: var userOrError = ...
var user = userRepository.GetUser(id);
if(user.IsFailure) { }
//Do stuff to the user
//instead of: userRepository.UpdateUser(userOrError.Value);
userRepository.UpdateUser(user);
I've noticed I find myself passing the result into other methods and forgetting the .Value
(which obviously causes a compile error). Also, I'm not a big fan of the name "Value", as it is a little too generic and adds a bit of clutter in my opinion. I'm curious about your thoughts on this change. What would be the potential issues/drawback with doing this approach?
Thanks
I could be wrong, but when I want to instantiate Maybe without value I have to use
new Maybe<SomeType>()
syntax.
For convenience reason could Maybe.None
static method be added like in some other Maybe libraries?
I'm writing an extension method that operates over a collection and executes some operation. It doesn't care about the output of that function, rather it returns back the original collection if the operation succeeds, else it returns a Result.Fail (and the error msg of the action) if it fails.
So instead of doing
DoSomething()
.OnSuccess(x => x.DoSomethingElse())
.OnSuccess(x => {
DoSomethingElse2()
return x
})
.OnSuccess(x => x.DoSomethingElse3())
I'd do something like
DoSomething()
.OnSuccess(x => x.DoSomethingElse())
.OnSuccess(x => x.Execute(() => DoSomethingElse2()))
.OnSuccess(x => x.DoSomethingElse3())
Where Execute
is a generic function defined as
public static Result<T> Execute<T>(this T collection, Func<Result> fn)
where T : IEnumerable
{
Result res = fn();
return res.IsSuccess
? Result.Ok(collection)
: Result.Fail<T>(res.Error);
}
Is Execute
an appropriate name in this case? I only care about whether an action method succeeds or not, but I'm never returning the actual results of that function so it seems misleading...how would you name that? Since I'm using your library, I figured I could use your expertise ๐
I am using the extensions for a method that gets some data from another system.
The 3 possible results of calling this method are:
If I use Maybe I can handle case 1 and 2 but case 3 is not expressed in the signature.
If I use Result I can handle 1 and 3 but I'm not sure how to handle case 2.
I guess it is possible to use Result<Maybe<DataObject>>. Is that the best option?
What are the guidelines for choosing Maybe versus Result?
Thanks,
Matthew
I'm trying to see what has changed between the 1.7.1 and 1.8.0 nuget package versions. Tagging the versions would help with that considerably.
I want to propose the feature of deconstructing Result
, so we can do
var (isSuccess, isFailure, error) = Result.Ok();
var (isSuccess, isFailure, somethingIsDisabled, error) = Result.Ok(true);
Motivation for me is the case when you have a primitive value, like bool
wrapped in Result
and we want to leave the monad e.g.
var serviceIsDisabled = CheckIfDisabled()
if (serviceIsDisabled.Value) // Do something
serviceIsDisabled.Value
in condition looks kinda ugly to me, so i've implemented deconstruction via extension methods. It would be nice to have it in the main package.
Please, let me know if you're ok with and i'll prepare PR with tests and stuff
We use this library in our project but we have one suggestion. It would be cool if it is able to configure the error message separator which is now the constant ", " on one central point.
For example:
Result.SetupErrorMessageSeparator("$$");
I know, it is able to pass the error message separator on method level like
Result.Combine("$$", r1, r2);
But this is not a solution for us because then we have to pass the separator on each usage of combine.
Do you see a opportunity to implement this?
I think I just determined that Maybe can't be used to hold enum's because it can currently only hold reference types, is this correct?
If so, I think it's because you've constrained T to "class" specifically.
public struct Maybe<T> : IEquatable<Maybe<T>> where T : class
Can you think of any easy way to achieve an enum type in a Maybe?
For the record, I was trying to implement text parsing adapters for enums and other primitive types into Maybe's. The goal was similar to what was achieved here:
https://github.com/louthy/language-ext
If you scroll down, you can see how they replaced TryParse with parseInt that returns an option.
Interestingly, they did not do am implementation for parseEnum, but in theory they could have.
For example the following test doesn't compile
public async Task<string> Promote_with_async_methods_in_the_middle_of_the_chain_but_not_starting_the_chain(long id)
{
var gateway = new EmailGateway();
// GetById rather than GetByIdAsync
return await GetById(id)
.ToResult("Customer with such Id is not found: " + id)
.Ensure(customer => customer.CanBePromoted(), "The customer has the highest status possible")
.OnSuccess(customer => customer.PromoteAsync())
.OnSuccess(customer => gateway.SendPromotionNotificationAsync(customer.Email))
.OnBoth(result => result.IsSuccess ? "Ok" : result.Error);
}
I suggest Appveyor for .NET projects but you can use both via git hooks
Hello,
I am using your library within a .NET 4.6.2 project and cannot access the Maybe.None static property.
The following does not compile (error is: 'Maybe' does not contain a definition for 'None'):
Maybe<MyClass> none = Maybe<MyClass>.None;
But this does:
Maybe<MyClass> none = null;
What's going on here?
Thanks,
Greg Valainis
Hi, can you add support for .NET core?
Pretty clear issue here, just needs an overload.
Result[] test1 = new Result[] { Result.Ok("one") };
Result.Combine(test1);
//Throws "Argument 1: cannot convert from 'CSharpFunctionalExtensions.Result[] to 'string'"
Result.Combine("dummy", test1);
//Throws "Argument 2: cannot convert from 'CSharpFunctionalExtensions.Result[] to 'CSharpFunctionalExtensions.Result'"
Is it possible to strong name the assemblies?
Many Thanks
My Visual Studio is very slow. Is there anyone else that have this issue?
Some method chains have 5-10 methods and the methods themselves have some small chains.
I'm using your Result object with a third-party library that communicates using exceptions. I'd like to lift the exceptions into a failed Result. My current code just returns a different Result from each branch of the try-catch.
I'm thinking something like:
Result.Try(() => library.ThingThatMightFail(),
(ex) => "Some error message, possibly branching on exception type...");
// Where
Try<Result<T>>(Func<T> attempt, Func<Exception,string> failure)
Handling specific exceptions can be done with ex is SomeException
, and truly unexpected exceptions can be rethrown.
This would just be a convenience method, but often the reason I'm using a Result is to hide library exceptions behind more user friendly error messages.
Hi,
I came across this issue while investigating a problem in other open project (NSubstitute). After some ffiddling I think I managed to trace it to the implementation of EqualityComparer for Maybe<>.
They say code is worth 1000 words, so here is the sample :
[Test]
public void Bug_MaybeDynamicNone()
{
var compareUsingMaybeDynamic = EqualityComparer<Maybe<dynamic>>.Default.Equals(Maybe<dynamic>.None, Maybe<dynamic>.None);
var compareObjectNotDynamic = EqualityComparer<object>.Default.Equals(Maybe<int>.None, Maybe<int>.None);
var compareDynamicInt = EqualityComparer<object>.Default.Equals((dynamic)1, (dynamic)1);
//NSubstitute is using object as generic type which doesnt work for Maybe<dynamic>
var compareUsingMaybeObject = EqualityComparer<object>.Default.Equals(Maybe<dynamic>.None, Maybe<dynamic>.None);
Assert.IsTrue(compareUsingMaybeDynamic);
Assert.IsTrue(compareObjectNotDynamic);
Assert.IsTrue(compareDynamicInt);
Assert.IsTrue(compareUsingMaybeObject); // Only comparission of Maybe<dynamic>.None using object comparer fails.
}
[Test]
public void Bug_DefaultInstance()
{
var compareMaybeIntUsingObject = EqualityComparer<object>.Default.Equals(Activator.CreateInstance(typeof(Maybe<int>)), Activator.CreateInstance(typeof(Maybe<int>)));
var compareUsingMaybeDynamic = EqualityComparer<Maybe<dynamic>>.Default.Equals(Activator.CreateInstance(typeof(Maybe<dynamic>)), Activator.CreateInstance(typeof(Maybe<dynamic>)));
//NSubstitute is using object as generic type which doesnt work for Maybe<dynamic>
var compareMaybeDynamicUsingObject = EqualityComparer<object>.Default.Equals(Activator.CreateInstance(typeof(Maybe<dynamic>)), Activator.CreateInstance(typeof(Maybe<dynamic>)));
Assert.IsTrue(compareMaybeIntUsingObject);
Assert.IsTrue(compareUsingMaybeDynamic); // Surprisingly this one fails for Default instance but not for Maybe<>.None
Assert.IsTrue(compareMaybeDynamicUsingObject); //Here is the same problem as with Maybe<dynamic>.None in the test above
}
I hope this will be enough to reproduce it. In case you need any more information let me know.
Regards
K.
Edit:
I found even simpler repro
var equals = Maybe<dynamic>.None == (dynamic)Maybe<dynamic>.None;
Assert.True(equals);
also found where the problem is Line 71 in Maybe<>
if (obj is T)
{
obj = new Maybe<T>((T)obj);
}
This check will pass for dynamic and will create new object with value in it which will fail down the line since current object HasNoValue while the newly created one Has.
Edit 2:
Actually
Maybe<dynamic>.None == (dynamic)Maybe<dynamic>.None;
Is a problem with Implicit conversion while my problems were in Equals.
Changing line 71 to
if (obj is T && !(obj is Maybe<T>))
fixes my problem but doesnt fix implicit conversion bug. I tried to find something to make also this one go away, but only thing I found was an infinite implicit conversion :)
Thats all I've got :) Hope this helps.
K.
I am using the ResultExtensions in a web API project. No matter what type of error happens in the railway oriented series of calls I am returning 400 (Bad Request). This is convenient and sufficient for now. Here is an excerpt of the code:
[Route("api/qrs/asbuilt/serialnumberstructure")]
[HttpPost]
public IHttpActionResult Post([FromBody] StructureRequestDto structureRequestDto)
{
return
ValidateRequest(structureRequestDto)
.OnSuccess(request => AddNextSerialNumberIfMissing(request))
.OnSuccess(request => AddPreviousSerialNumberIfMissing(request))
.OnSuccess(request => AddStructureIfMissing(request))
.OnSuccess(request => GetStructure(request))
.Ensure(response => response.HasValue, FormatStructureNotFoundError(structureRequestDto))
.Map(response => response.Value)
.OnBoth(
response =>
response.IsSuccess ?
this.Ok(response.Value) as IHttpActionResult :
this.BadRequest(response.Error) as IHttpActionResult);
}
I would like to improve the accuracy of my responses and return 500 for system errors and 400 when the error is caused by the input data. Because the error is string I'm not sure of a good way to do this. I thought about putting the HTTP status codes into the error strings and then parse at the end. That might work.
Are there any suggestions that would help me to be more specific with my error responses?
Thanks
Hi, is there a reason that Ensure method creates a new instances of result?
We can just return current result object if current result.IsFailure.
` public static Result Ensure(this Result result, Func<T, bool> predicate, string errorMessage)
{
if (result.IsFailure)
return Result.Fail(result.Error);
if (!predicate(result.Value))
return Result.Fail<T>(errorMessage);
return Result.Ok(result.Value);
}`
Shouldn't Combine<T>
return a Result<T>
instead of Result
?
I have following method:
public async Task<Result<MyOrderModel>> GetMyOrderByOrderId(string subject, long orderId)
{
long customerId = 0;
Action<Customer> keepCustomerId = (c) => customerId = c.Id;
return await GetCustomerBySubjectAsync(subject)
.ToResult(ErrorMessage.CustomerNotFound)
.OnSuccess(customer => keepCustomerId(customer))
.OnSuccess(customer => GetOrderByIdAsync(orderId).ToResult(ErrorMessage.OrderNotFound))
.Ensure(order => order.Customer.Id == customerId, ErrorMessage.AmbigiousPrincipalCall)
.OnSuccess(order => _mapper.Map<MyOrderModel>(order));
}
public async Task<Maybe<Customer>> GetCustomerBySubjectAsync(string sub)
{
return await _context.Customers.Where(c => c.Sub == sub).FirstOrDefaultAsync();
}
In the Ensure statement I need access to a result of GetCustomerBySubjectAsync.
For that reason I'm storing customerId in the very first OnSuccess method.
I'm sure there is a more elegant way?
Thanks in advance.
Is it possible to call async methods, or do I need to use Result
or Wait()
?
var totalCost = Orders.Sum(s =>
{
return OperationOnOrder(s)
.OnBoth(r => r.IsSuccess ? r.Value.Cost: 0)
;
});
So OperationOnOrder returns
Result<SomethingElse>
Now, the problem is that I can not "jump out" when OperationOnOrder returns failure, I can only return a decimal value (which sum can handle), so what I do this return 0, but I want to return failure rather than 0.
Any suggestion?
Contrary to what I expected, Maybe<(ClassA, ClassB)>.None.Hasvalue
is true.
It's more an information than an issue and surely I could try a PR to support tuples but tought it'd be good to mention it somehwhere.
Trying to install the package into .NET 4.0 project, but it fails. Is .NET 4.0 not supported or is something else going wrong?
Error:
Install failed. Rolling back...
Install-Package : Could not install package 'CSharpFunctionalExtensions 1.7.1'. You are trying to install this package into a project that targets '.NETFramework,Version=v4.0', but the package does not contain any assembly references or content files that are
compatible with that framework. For more information, contact the package author.
At line:1 char:16
+ Install-Package <<<< CSharpFunctionalExtensions
+ CategoryInfo : NotSpecified: (:) [Install-Package], InvalidOperationException
+ FullyQualifiedErrorId : NuGetCmdletUnhandledException,NuGet.PowerShell.Commands.InstallPackageCommand
PM>
Hi! I have a personal project with a class similar to Result<T>
and I have the following method, which is used to wrap code on Result
structure.
I am planning to submit a PR for this. What do you think?
Code
public static Result<T> ToResult<T>(this Func<T> func)
{
try
{
return Result.Ok(func());
}
catch (Exception ex)
{
return Result.Fail<T>(ex.Message);
}
}
Usage
public Result<TDTO> GetById(TKey key)
{
return Result.ToResult(() => ToDTO(Repository.GetById(key)));
}
Currently Result.Error is of type string.
IMHO it would make the Result object more expressive if Error where generic (or object).
Use case: Collecting error messages.
I have a heavy usage of your functional library, it is doing a great job as part of API project. The problem I noticed is that after calling OnSuccess or ToResult on async methods current thread culture is lost because you are using ConfigureAwait(false) as default which doesn't return to thread caller, but to first available thread. I know OnSuccess and ToResult have parameter bool continueOnCapturedContext = false that I can set to true, but I wouldn't want to do this on every place in code. So my question actually is, why do you have ConfigureAwait(false) in your library when .NET is continuing on captured context ( ConfigureAwait(true) ) by default? And is it possible that you set continueOnCapturedContext to have default value = true?
I searched the repo and found no reference to flatMap, but perhaps Map is overloaded with similar functionality or something. Can you please clarify?
I am running into scenarios like this and perhaps flatMap would be the typical functional solution.
FirstTask()
.Map(inputObject => CreateSomeResultOfClient(inputObject))
.Map(client => client.DoSomething()) <----Doesn't work because client is Result
.OnSuccess(string => Console.WriteLine(string));
.OnFailure(error => Console.WriteLine(error));
When using the the package in an asse,bly which is signed with strong name, I am not able to do so.
Could you please sign it?
This works:
private IObservable<Unit> MyMethod() => MyApi
.DoSomething()
.Select(result => ResultExtensions
.OnSuccess(result, () => { _myVariable = result.Value; })
.OnSuccess(() => NavigationService.GoBack())
.OnFailure(async error => await NavigationService.DisplayAlert(error))
.OnBoth(_ => Unit.Default));
this doesn't:
private IObservable<Unit> MyMethod() => MyApi
.DoSomething()
.Select(result => result
.OnSuccess(() => { _myVariable = result.Value; })
.OnSuccess(() => NavigationService.GoBack())
.OnFailure(async error => await NavigationService.DisplayAlert(error))
.OnBoth(_ => Unit.Default));
On the one that fails, R# complains that the first OnSuccess
is missing a return statement;
Note that even on the first version I have to add ugly braces otherwise it thinks my Action
is a Func
.
The following code:
IEnumerable<string> list = new List<string>();
Maybe<IEnumerable<string>> maybeList = list; // line in error
gives me the error
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable' to 'CSharpFunctionalExtensions.Maybe<System.Collections.Generic.IEnumerable>'
Instead, the following code, works perfectly:
List<string> list = new List<string>();
Maybe<IEnumerable<string>> maybeList = list;
Any hint?
Hi Vladimir,
I really enjoyed your PluralSight course and I really appreciate the thoughts which went into this library.
However, I am not entirely sure how to use the Result struct together with a cancelable task (e.g if I have a method, that returns a Task<Result>).
How would you handle the task cancellation? Returning an error result seems to be somehow wrong. I also though about returning a Result<Maybe>
Is it possible to create an additional overload for typed failed exceptions? As far as I can see, only an error-message is supported. But the type of exception is for example easy for unit-testing :)
cheers!
I think it would be nice to be able to implicitly convert Maybe<T>
to T.
By adding this piece of code to Maybe<T>
:
public static implicit operator T(Maybe<T> maybe)
{
return maybe._value;
}
it will be possible to do something like this:
Maybe<int> maybe = 666;
int value = maybe;
int result = value + maybe + value;
It will also allow passing directly Maybe to functions with T parameter.
The following worked on 1.7.1 but I just updated to 1.8.1 and now it fails to match the appropriate overload on ensure.
Here was my original code:
return GetAuthenticationContext(authority)
.OnSuccess(context => AcquireTokenAsync(context, resource))
.Ensure(authResult => authResult != null, "Failed to Obtain JWT")
.Map(authResult => authResult.AccessToken)
.Result.Value;
AcquireTokenAsync()
returns Task<Result<T>>
Heres the overload it used to match in AsyncResultExtensionsLeftOperands.cs
public static async Task<Result<T>> Ensure<T>(this Task<Result<T>> resultTask, Func<T, bool> predicate, string errorMessage)
{
Result<T> result = await resultTask;
if (result.IsFailure)
return Result.Fail<T>(result.Error);
if (! predicate(result.Value))
return Result.Fail<T>(errorMessage);
return Result.Ok(result.Value);
}
My guess is that it's related to the new param bool continueOnCapturedContext = false
, but in theory the default parameter should have maintained backward compatibility... so I don't know what the deal is. If I find time, I'll try to write a new test and troubleshoot.
First, this is an awesome library!! I have been using it for several days and I love the control flow.
One change I'd like to suggest is to the Maybe module. It works great if you want to expose a Maybe as a property. But I'd like to be able to create a new Maybe object and pass it on.
For example, an error occurs and I just want to ignore it and return a Maybe. Currently, it seems like you have to create a new variable and return it. This is a little ugly in a lambda.
.OnBoth(result =>
{
Maybe<CachedVehicle> ret = result.IsSuccess ? result.Value : null;
return Result.Ok(ret);
})
I'd prefer to do something like this:
.OnBoth(result => Result.Ok(Maybe<CachedVehicle>(result.IsSuccess ? result.Value : null))
Does that make sense? Did I miss something and maybe this is already possible?
Hi!
I'm trying to have optional DateTime. But when I do Maybe<DateTime>.None
, it uses the default value which is 0001-01-01, and when I do .HasValue
, it's true.
Thanks !
I can't return Result from a webapi as in case of success, Error throws an exception of type 'System.InvalidOperationException'. Why not just return string.empty?
Increase code coverage and add unit tests for not tested methods
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.