Analytics-CSharp (C#)
With Analytics-CSharp, you can add Segment analytics to your C# based app which includes Unity, Xamarin, or .NET. Analytics-CSharp helps you measure your users, product, and business. It unlocks insights into your app's funnel, core business metrics, and whether you have product-market fit. The Analytics-CSharp library is open-source on GitHub.
These platforms support Analytics-CSharp:
- .NET/.NET core/.NET framework
- Mono
- Universal Windows platform
- Xamarin
- iOS
- Mac
- Android
- Unity
- iOS
- Android
- PC, Mac, Linux
Info
To migrate to Analytics-CSharp from Analytics.NET or Analytics.Xamarin, follow the steps in the Analytics-CSharp migration guide.
To get started with the Analytics-CSharp library:
-
Create a source in Segment.
- Go to Connections > Sources > Add Source.
- Search for Xamarin, Unity, or .NET (whichever source you want to use) and click Add Source. Note: There is no CSharp source. To use Analytics-CSharp, use either Xamarin, Unity, or .NET as your source.
-
Add the Analytics dependency to your project. Analytics-CSharp is distributed through NuGet. Check other installation options on the Segment.Analytics.CSharp NuGet page.
dotnet add package Segment.Analytics.CSharp --version <LATEST_VERSION>For Unity, Analytics-CSharp is distributed through OpenUPM. Read more about OpenUPM.
openupm add com.segment.analytics.csharp -
Initialize and configure the client.
1// NOTE: to make Analytics stateless/in-memory,2// add `InMemoryStorageProvider` to the configuration3var configuration = new Configuration("<YOUR WRITE KEY>",4flushAt: 20,5flushInterval: 30);6var analytics = new Analytics(configuration);
Info
Segment's SDK is designed to be disposable, meaning Segment disposes of objects when the analytics instance is disposed. Segment avoids using singletons for configurations or HTTP clients to prevent memory management issues. If you want to use singletons, create your own HTTP client provider with a singleton HTTP client for better control and management.
| Option name | Description |
|---|---|
writeKey required | This is your Segment write key. |
flushAt | The default is set to 20. The count of events at which Segment flushes events. |
flushInterval | The default is set to 30 (seconds). The interval in seconds at which Segment flushes events. |
defaultSettings | The default is set to {}. The settings object used as fallback in case of network failure. |
autoAddSegmentDestination | The default is set to true. This automatically adds the Segment Destination plugin. You can set this to false if you want to manually add the Segment Destination plugin. |
apiHost | The default is set to api.segment.io/v1. This sets a default API Host to which Segment sends events. |
cdnHost | The default is set to cdn-settings.segment.com/v1. This sets a default cdnHost to which Segment fetches settings. |
analyticsErrorHandler | The default is set to null. This sets an error handler to handle errors happened in analytics. |
storageProvider | The default is set to DefaultStorageProvider. This sets how you want your data to be stored. DefaultStorageProvider is used by default which stores data to local storage. InMemoryStorageProvider is also provided in the library. You can also write your own storage solution by implementing IStorageProvider and IStorage. |
httpClientProvider | The default is set to DefaultHTTPClientProvider. This sets a http client provider for analytics use to do network activities. The default provider uses System.Net.Http for network activities. |
flushPolicies | The default is set to null. This sets custom flush policies to tell analytics when and how to flush. By default, it converts flushAt and flushInterval to CountFlushPolicy and FrequencyFlushPolicy. If a value is given, it overwrites flushAt and flushInterval. |
eventPipelineProvider | The default is EventPipelineProvider. This sets a custom event pipeline to define how Analytics handles events. The default EventPipelineProvider processes events asynchronously. Use SyncEventPipelineProvider to make manual flush operations synchronous. |
Once you've installed the Analytics-CSharp library, you can start collecting data through Segment's tracking methods:
Info
For any of the different methods described, you can replace the properties and traits in the code samples with variables that represent the data collected.
The Identify method lets you tie a user to their actions and record traits about them. This includes a unique user ID and any optional traits you know about them like their email, name, address. The traits option can include any information you want to tie to the user. When using any of the reserved traits, be sure the information reflects the name of the trait. For example, email should always be a string of the user's email address.
1analytics.Identify("user-123", new JsonObject {2["username"] = "MisterWhiskers",3["email"] = "hello@test.com",4["plan"] = "premium"5});
The Track method lets you record the actions your users perform. Every action triggers an event, which also has associated properties that the track method records.
1analytics.Track("View Product", new JsonObject {2["productId"] = 123,3["productName"] = "Striped trousers"4});
The Screen method lets you record whenever a user sees a screen in your mobile app, along with optional extra information about the page being viewed.
You'll want to record a screen event whenever the user opens a screen in your app. This could be a view, fragment, dialog or activity depending on your app.
Not all integrations support Screen events. When it's not supported explicitly, the screen method tracks as an event with the same parameters.
1analytics.Screen("ScreenName", new JsonObject {2["productSlug"] = "example-product-123"3});
The Page method lets you record whenever a user sees a page in your web app, along with optional extra information about the page.
You'll want to record a page event whenever the user opens a page in your app. This could be a webpage, view, fragment, dialog or activity depending on your app.
Not all integrations support Page events. When it's not supported explicitly, the page method tracks as an event with the same parameters.
1analytics.Page("PageName", new JsonObject {2["productSlug"] = "example-product-123"3});
The Group method lets you associate an individual user with a group — whether it's a company, organization, account, project, or team. This includes a unique group identifier and any additional group traits you may have, like company name, industry, number of employees. You can include any information you want to associate with the group in the traits option. When using any of the reserved group traits, be sure the information reflects the name of the trait. For example, email should always be a string of the user's email address.
1analytics.Group("user-123", new JsonObject {2["username"] = "MisterWhiskers",3["email"] = "hello@test.com",4["plan"] = "premium"5});
Segment's plugin architecture enables you to modify and augment how the analytics client works. From modifying event payloads to changing analytics functionality, plugins help to speed up the process of getting things done.
Plugins are run through a timeline, which executes in order of insertion based on their entry types. Segment has these 5 entry types:
| Type | Details |
|---|---|
Before | Executes before event processing begins. |
Enrichment | Executes as the first level of event processing. |
Destination | Executes as events begin to pass off to destinations. |
After | Executes after all event processing completes. You can use this to perform cleanup operations. |
Utility | Executes only with manual calls such as Logging. |
There are 3 basic types of plugins that you can use as a foundation for modifying functionality. They are: Plugin, EventPlugin, and DestinationPlugin.
Plugin acts on any event payload going through the timeline.
For example, if you want to add something to the context object of any event payload as an enrichment:
1class SomePlugin : Plugin2{3public override PluginType Type => PluginType.Enrichment;4public override RawEvent Execute(RawEvent incomingEvent)5{6incomingEvent.Context["foo"] = "bar";7return incomingEvent;8}9}
EventPlugin is a plugin interface that acts on specific event types. You can choose the event types by only overriding the event functions you want.
For example, if you only want to act on Track and Identify events:
1class SomePlugin : EventPlugin2{3public override PluginType Type => PluginType.Enrichment;4public override IdentifyEvent Identify(IdentifyEvent identifyEvent)5{6// code to modify identify event7return identifyEvent;8}9public override TrackEvent Track(TrackEvent trackEvent)10{11// code to modify track event12return trackEvent;13}14}
The DestinationPlugin interface is commonly used for device-mode destinations. This plugin contains an internal timeline that follows the same process as the analytics timeline, enabling you to modify and augment how events reach a particular destination.
For example, if you want to implement a device-mode destination plugin for Amplitude, you can use this:
1class AmplitudePlugin : DestinationPlugin2{3public override string Key =>4"Amplitude"; // This is the name of the destination plugin, it is used to retrieve settings internally5private Amplitude amplitudeSDK: // This is an instance of the partner SDK6public AmplitudePlugin()7{8amplitudeSDK = Amplitude.instance;9amplitudeSDK.initialize(applicationContext, "API_KEY");10}11/*12* Implementing this function allows this plugin to hook into any track events13* coming into the analytics timeline14*/15public override TrackEvent Track(TrackEvent trackEvent)16{17amplitudeSDK.logEvent(trackEvent.Event);18return trackEvent;19}20}
configure(Analytics): Use this function to set up your plugin. This implicitly calls once the plugin registers.update(Settings): Use this function to react to any settings updates. This implicitly calls when settings update. You can force a settings update by callinganalytics.checkSettings().DestinationPlugintimeline: The destination plugin contains an internal timeline that follows the same process as the analytics timeline, enabling you to modify/augment how events reach the particular destination. For example, if you only wanted to add a context key when sending an event toAmplitude:
1class AmplitudeEnrichment : Plugin2{3public override PluginType Type => PluginType.Enrichment;4public override RawEvent Execute(RawEvent incomingEvent)5{6incomingEvent.Context["foo"] = "bar";7return incomingEvent;8}9}10var amplitudePlugin = new AmplitudePlugin(); // add amplitudePlugin to the analytics client11analytics.Add(amplitudePlugin);12amplitudePlugin.Add(new AmplitudeEnrichment()); // add enrichment plugin to amplitude timeline
Adding plugins enable you to modify your analytics implementation to best fit your needs. You can add a plugin using this:
1var yourPlugin = new SomePlugin()2analytics.Add(yourPlugin)
Though you can add plugins anywhere in your code, it's best to implement your plugin when you configure the client.
Here's an example of adding a plugin to the context object of any event payload as an enrichment:
1class SomePlugin : Plugin2{3public override PluginType Type => PluginType.Enrichment;4public override RawEvent Execute(RawEvent incomingEvent)5{6incomingEvent.Context["foo"] = "bar";7return incomingEvent;8}9}10var yourPlugin = new SomePlugin()11analytics.Add(yourPlugin)
See how other platforms and languages use Analytics-CSharp in different example projects.
The Analytics-CSharp utility methods help you work with plugins from the analytics timeline. They include:
The Add method lets you add a plugin to the Analytics timeline.
1class SomePlugin : Plugin2{3public override PluginType Type => PluginType.Enrichment;4public override RawEvent Execute(RawEvent incomingEvent)5{6incomingEvent.Context["foo"] = "bar";7return incomingEvent;8}9}10var somePlugin = new SomePlugin();11analytics.Add(somePlugin);
The Find method lets you find a registered plugin from the analytics timeline.
var plugin = analytics.Find<SomePlugin>();
The Remove methods lets you remove a registered plugin from the analytics timeline.
analytics.remove(somePlugin);
The Flush method lets you force flush the current queue of events regardless of what the flushAt and flushInterval is set to.
analytics.Flush();
The Reset method clears the SDK's internal stores for the current user and group. This is useful for apps where users log in and out with different identities on the same device over time.
analytics.Reset()
To modify the properties of an event, you can either write an enrichment plugin that applies changes to all events, or pass an enrichment closure to the analytics call to apply changes to a specific event.
1analytics.Track("MyEvent", properties, @event =>2{3if (@event is TrackEvent trackEvent)4{5// update properties of this event6trackEvent.UserId = "foo";7}89return @event;10});
For more granular control of when events are uploaded you can use FlushPolicies.
Warning
FlushPolicies overrides any setting on flushAt and flushInterval, but you can use CountFlushPolicy and FrequencyFlushPolicy to have the same behavior.
A Flush Policy defines the strategy for deciding when to flush. This can be on an interval, on a certain time of day, after receiving a certain number of events or even after receiving a particular event. This gives you even more flexibility on when to send an event to Segment.
To make use of flush policies you can set them in the configuration of the client:
1var configuration = new Configuration("<YOUR WRITE KEY>",2flushPolicies: new List<IFlushPolicy>3{4new CountFlushPolicy(),5new FrequencyFlushPolicy(),6new StartupFlushPolicy()7});8var analytics = new Analytics(configuration);
That means only the first policy to reach ShouldFlush gets to trigger a flush at a time. In the example above either the event count gets to 5 or the timer reaches 500ms, whatever comes first will trigger a flush.
Segment has several standard FlushPolicies:
CountFlushPolicytriggers whenever a certain number of events is reachedFrequencyFlushPolicytriggers on an interval of millisecondsStartupFlushPolicytriggers on client startup only
One of the main advantages of FlushPolicies is that you can choose to add and remove policies. This is very powerful when you want to reduce or increase the amount of flushes.
For example, you might want to disable flushes if you detect the user has no network:
1// listen to network changes2if (noNetwork) {3// remove all flush policies to avoid flushing4analytics.ClearFlushPolicies();56// or disable analytics completely (including store events)7analytics.Enable = false8}9else {10analytics.AddFlushPolicy(new CountFlushPolicy(), new FrequencyFlushPolicy());11}
You can create a custom FlushPolicy special for your application needs by implementing the IFlushPolicy interface. You can also extend the IFlushPolicy class that already creates and handles the shouldFlush value reset.
A FlushPolicy only needs to implement two of these methods:
Schedule: Executed when the flush policy is enabled and added to the client. This is a good place to start background operations, make async calls, configure things before executionUpdateState: Gets called on every event tracked by your clientUnschedule: Called when policy should stop running any scheduled flushesReset: Called after a flush is triggered (either by your policy, by another policy or manually)
Your FlushPolicy should also have a ShouldFlush observable boolean value. When this is set to true the client attempts to upload events. Each policy should reset this value to false according to its own logic.
1class FlushOnScreenEventsPolicy : IFlushPolicy2{3private bool _screenEventsSeen = false;45public bool ShouldFlush() => _screenEventsSeen;67public void UpdateState(RawEvent @event)8{9// Only flush when at least a screen even happens10if (@event is ScreenEvent)11{12_screenEventsSeen = true;13}14}1516public void Reset()17{18_screenEventsSeen = false;19}2021public void Schedule(Analytics analytics) {}2223public void Unschedule() {}24}
You can handle analytics client errors through the analyticsErrorHandler option.
The error handler configuration requires an instance that implements IAnalyticsErrorHandler which will get called whenever an error happens on the analytics client. It will receive a general Exception, but you can check if the exception is a type of AnalyticsError and converts to get more info about the error. Check out the @segmentio/Analytics-CSharp repository to see a full list of error types that analytics throws.
You can use this error handling to trigger different behaviors in the client when a problem occurs. For example if the client gets rate limited you could use the error handler to swap flush policies to be less aggressive:
1class NetworkErrorHandler : IAnalyticsErrorHandler2{3private Analytics _analytics;45public NetworkErrorHandler(Analytics analytics)6{7_analytics = analytics;8}910public void OnExceptionThrown(Exception e)11{12if (e is AnalyticsError error && error.ErrorType == AnalyticsErrorType.NetworkServerLimited)13{14_analytics.ClearFlushPolicies();15// Add less persistent flush policies16_analytics.AddFlushPolicy(new CountFlushPolicy(1000), new FrequencyFlushPolicy(60 * 60 * 1000));17}18}19}
Plugins can also report errors to the handler by using the .ReportInternalError function of the analytics client, we recommend using the AnalyticsErrorType.PluginError for consistency, and attaching the exception with the actual exception that was hit:
1try2{3// do something;4}5catch (Exception e)6{7this.Analytics.ReportInternalError(AnalyticsErrorType.PluginError, e, "Error from plugin");8Analytics.Logger.Log(LogLevel.Error, e);9}
Besides error handling, you could also provide a static ISegmentLogger to help log and debug as well as error handling. The same log that is reported by ReportInternalError is also reported to this static logger. The static logger also receives more errors and exceptions because it does not require an Analytics instance available. Thus, it's also a good idea to use the logger as an addition to IAnalyticsErrorHandler.
1Analytics.Logger = new SegmentLogger();23class SegmentLogger : ISegmentLogger4{5public void Log(LogLevel logLevel, Exception exception = null, string message = null)6{7switch (logLevel)8{9case LogLevel.Warning:10case LogLevel.Information:11case LogLevel.Debug:12Console.Out.WriteLine("Message: " + message);13break;14case LogLevel.Critical:15case LogLevel.Trace:16case LogLevel.Error:17Console.Error.WriteLine("Exception: " + exception?.StackTrace);18Console.Error.WriteLine("Message: " + message);19break;20case LogLevel.None:21default:22break;23}24}25}
The SDK allows you to have full control over the network components. You can easily swap out System.Net with your favorite network library by implementing IHTTPClientProvider and extending HTTPClient. Take a look at this example where the default http client is fully replaced by Unity's UnityWebRequest.
You can also redirect the HTTP calls to your own proxy server by implementing IHTTPClientProvider and extending DefaultHTTPClient:
1class ProxyHttpClient : DefaultHTTPClient2{3public ProxyHttpClient(string apiKey, string apiHost = null, string cdnHost = null) : base(apiKey, apiHost, cdnHost)4{5}67public override string SegmentURL(string host, string path)8{9if (host.Equals(_apiHost))10{11return "Your proxy api url";12}13else14{15return "Your proxy cdn url";16}17}18}1920class ProxyHttpClientProvider : IHTTPClientProvider21{22public HTTPClient CreateHTTPClient(string apiKey, string apiHost = null, string cdnHost = null)23{24return new ProxyHttpClient(apiKey, apiHost, cdnHost);25}26}
The SDK also allows you to fully customize your storage strategy. It comes with two standard providers: DefaultStorageProvider that stores data to local disk and InMemoryStorageProvider that stores data all in memory. You can write up your own provider according to your needs, for example, store data to a database or to your own server directly, by implementing IStorage and IStorageProvider. Please refer to the implementation of Storage as an example.
The SDK supports .netstandard 1.3 and .netstandard 2.0 and auto assembles the internal Json library according to the target framework:
- On
.netstandard 1.3, the SDK uses Newtonsoft Json.NET - On
.netstandard 2.0, the SDK uses System.Text.Json
To send an array as an event property, reference the Serialization.NET GitHub repo. Below is an example of code you can implement to send an array of strings:
1List<string> listOfStrings = new List<string> { "test1", "test2", "test3" };23JsonObject customerJsonObj = new JsonObject4{5["event_name"] = new JsonArray(listOfStrings.ConvertAll(o => (JsonElement)o))6};
For sample usages of the SDK in specific platforms, checkout the following:
| Platform | Sample |
|---|---|
| Asp.Net | Set up with dependency injection |
| Asp.Net MVC | Set up with dependency injection |
| Console | Basic setup |
| Unity | Singleton Analytics |
| Lifecycle plugin | |
| Custom HTTPClient | |
| Xamarin | Basic setup |
| General | Custom HTTP client |
| Custom Storage | |
| Flush Policy | |
| Custom Logger | |
| Custom Error Handler |
This library targets .NET Standard 1.3 and .NET Standard 2.0. See the list of compatible platforms.
The C# library adds the sentAt timestamp to event payloads to improve efficiency. This may affect the timestamp field calculated by Segment when operating in offline mode. For more details, see the timestamp documentation.